封面图片

Elemental Design 模式

Elemental Design Patterns

杰森·麦克·史密斯

Jason McC. Smith

图像

新泽西州上马鞍河 • 波士顿 • 印第安纳波利斯 • 纽约旧金山

• 多伦多 • 蒙特利尔 • 伦敦 • 慕尼黑 • 巴黎 • 马德里

开普敦 • 悉尼 • 东京 • 新加坡 • 墨西哥城

Upper Saddle River, NJ • Boston • Indianapolis • San Francisco

New York • Toronto • Montreal • London • Munich • Paris • Madrid

Capetown • Sydney • Tokyo • Singapore • Mexico City

制造商和卖家用来区分其商品的许多名称都声称是商标。如果这些名称出现在本书中,并且出版商知道商标索赔,则这些名称已使用首字母大写字母或全部大写字母印刷。

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.

作者和出版商在编写本书时已谨慎行事,但不作任何形式的明示或暗示的保证,也不对错误或遗漏承担任何责任。对于与使用此处包含的信息或程序有关或由此引起的附带或间接损害,我们不承担任何责任。

The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.

当批量订购用于批量购买或特价销售时,出版商会为这本书提供极好的折扣,其中可能包括电子版本和/或定制封面以及特定于您的业务、培训目标、营销重点和品牌兴趣的内容。欲了解更多信息,请联系:

The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact:

美国企业和政府销售

部 (800) 382-3419

corpsales@pearsontechgroup.com

U.S. Corporate and Government Sales

(800) 382-3419

corpsales@pearsontechgroup.com

对于美国以外的销售,请联系:

For sales outside the United States, please contact:

国际销售

international@pearson.com

International Sales

international@pearson.com

请访问我们的网站:informit.com/aw

Visit us on the Web: informit.com/aw

美国国会图书馆出版物编目数据

Library of Congress Cataloging-in-Publication Data

史密斯,杰森·麦克 (Jason McColm)

元素设计模式 / 杰森·麦克·史密斯。



包括参考书目和索引。

ISBN 0-321-71192-0 (精装:alk. paper) 1.软件模式。2. 软件架构

3.系统设计。I. 标题。

QA76.76.P37S657 2012

005.1—dc23

2012001271

Smith, Jason McC. (Jason McColm)

  Elemental design patterns / Jason McC. Smith.

    p. cm.

  Includes bibliographical references and index.

  ISBN 0-321-71192-0 (hardcover : alk. paper) 1. Software patterns. 2. Software architecture

3. System design. I. Title.

  QA76.76.P37S657 2012

  005.1—dc23

                                                                               2012001271

版权所有 © 2012 Pearson Education, Inc.

Copyright © 2012 Pearson Education, Inc.

保留所有权利。在美国印刷。本出版物受版权保护,在以任何形式或任何方式(电子、机械、影印、录制或类似方式)进行任何禁止的复制、存储在检索系统中或传输之前,必须获得出版商的许可。要获得使用本作品资料的许可,请向 Pearson Education, Inc. 提交书面请求,地址为 Permissions Department, One Lake Street, Upper Saddle River, New Jersey 07458,或者您可以将您的请求传真至 (201) 236-3290。

All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. To obtain permission to use material from this work, please submit a written request to Pearson Education, Inc., Permissions Department, One Lake Street, Upper Saddle River, New Jersey 07458, or you may fax your request to (201) 236-3290.

国际标准书号13: 978-0-321-71192-2

国际标准书号10: 0-321-71192-0

ISBN 13: 978-0-321-71192-2

ISBN 10:        0-321-71192-0

文本在美国马萨诸塞州韦斯特福德的 Courier 使用再生纸印刷。

Text printed in the United States on recycled paper at Courier in Westford, Massachusetts.

首次印刷,2012 年 3 月

First printing, March, 2012

对于 B.

您在这段旅程的开始就在那里,

我希望您能够看到终点。

For B.

You were there at the beginning of this journey,

I wish you’d been able to see the end.

内容

Contents

数字

Figures

Tables

清单

Listings

前言

Foreword

前言

Preface

确认

Acknowledgments

关于作者

About the Author

1 设计模式简介

1 Introduction to Design Patterns

1.1 部落沉思

1.1 Tribal Musings

1.2 艺术还是科学?

1.2 Art or Science?

1.2.1 以死记硬背的方式查看模式

1.2.1 Viewing Patterns as Rote

1.2.2 依赖于语言的视图

1.2.2 Language-Dependent Views

1.2.3 从神话到科学

1.2.3 From Myth to Science

2 种元素设计模式

2 Elemental Design Patterns

2.1 背景

2.1 Background

2.2 地点、原因、方式

2.2 The Where, the Why, the How

2.2.1 Decorator 的分解

2.2.1 Decomposition of Decorator

2.2.2 进入兔子洞

2.2.2 Down the Rabbit Hole

2.2.3 背景信息

2.2.3 Context

2.2.4 设计空间

2.2.4 The Design Space

2.3 核心 EDP

2.3 Core EDPs

2.4 总结

2.4 Conclusion

3 Pattern 实例表示法

3 Pattern Instance Notation

3.1 基础

3.1 Basics

3.2 密码盒

3.2 The PINbox

3.2.1 折叠的密码框

3.2.1 Collapsed PINbox

3.2.2 标准 PIN 码盒

3.2.2 Standard PINbox

3.2.3 扩展的 PIN 码框

3.2.3 Expanded PINbox

3.2.4 堆叠 PIN 盒和多重性

3.2.4 Stacked PINboxes and Multiplicity

3.2.5 剥离和聚结

3.2.5 Peeling and Coalescing

3.3 总结

3.3 Conclusion

4 使用 EDP

4 Working with EDPs

4.1 模式的组合

4.1 Composition of Patterns

4.1.1 同位素

4.1.1 Isotopes

4.2 重新创建 Decorator

4.2 Recreating Decorator

4.3 重构

4.3 Refactoring

4.4 大局观

4.4 The Big Picture

4.5 为什么您可能想要阅读附录

4.5 Why You May Want to Read the Appendix

4.6 高级主题

4.6 Advanced Topics

4.6.1 重点文档和培训

4.6.1 Focused Documentation and Training

4.6.2 指标

4.6.2 Metrics

4.6.3 程序分析

4.6.3 Procedural Analysis

4.7 总结

4.7 Conclusion

5 EDP 目录

5 EDP Catalog

创建对象

Create Object

取回

Retrieve

遗产

Inheritance

抽象界面

Abstract Interface

代表团

Delegation

重定向

Redirection

聚集

Conglomeration

递归

Recursion

还原方法

Revert Method

扩展方法

Extend Method

委托集团

Delegated Conglomeration

重定向递归

Redirected Recursion

受信任的委派

Trusted Delegation

受信任的重定向

Trusted Redirection

代理代表团

Deputized Delegation

代理重定向

Deputized Redirection

6 种中间 Pattern 组合

6 Intermediate Pattern Compositions

Fulfill 方法

Fulfill Method

检索新

Retrieve New

检索共享

Retrieve Shared

对象化器

Objectifier

对象递归

Object Recursion

7 四人组模式组合

7 Gang of Four Pattern Compositions

7.1 创建模式

7.1 Creational Patterns

7.1.1 抽象工厂

7.1.1 Abstract Factory

7.1.2 工厂方法

7.1.2 Factory Method

7.2 结构模式

7.2 Structural Patterns

7.2.1 装饰器

7.2.1 Decorator

7.2.2 代理

7.2.2 Proxy

7.3 行为模式

7.3 Behavioral Patterns

7.3.1 责任链

7.3.1 Chain of Responsibility

7.3.2 模板方法

7.3.2 Template Method

7.4 总结

7.4 Conclusion

A ρ 微积分

A ρ-Calculus

A.1 Reliance 运算符

A.1 Reliance Operators

A.2 传递性和同位素

A.2 Transitivity and Isotopes

A.3 相似性

A.3 Similarity

A.4 EDP 形式

A.4 EDP Formalisms

A.5 组合和缩减规则

A.5 Composition and Reduction Rules

A.6 模式实例表示法和角色

A.6 Pattern Instance Notation and Roles

A.7 EDP 定义

A.7 EDP Definitions

A.7.1 创建对象

A.7.1 Create Object

A.7.2 检索

A.7.2 Retrieve

A.7.3 继承

A.7.3 Inheritance

A.7.4 抽象接口

A.7.4 Abstract Interface

A.7.5 委派

A.7.5 Delegation

A.7.6 重定向

A.7.6 Redirection

A.7.7 企业集团

A.7.7 Conglomeration

A.7.8 递归

A.7.8 Recursion

A.7.9 还原方法

A.7.9 Revert Method

A.7.10 Extend 方法

A.7.10 Extend Method

A.7.11 委托企业集团

A.7.11 Delegated Conglomeration

A.7.12 重定向递归

A.7.12 Redirected Recursion

A.7.13 受信任的委派

A.7.13 Trusted Delegation

A.7.14 可信重定向

A.7.14 Trusted Redirection

A.7.15 代理委派

A.7.15 Deputized Delegation

A.7.16 代理重定向

A.7.16 Deputized Redirection

A.8 中间模式定义

A.8 Intermediate Pattern Definitions

A.8.1 Fulfill 方法

A.8.1 Fulfill Method

A.8.2 检索新

A.8.2 Retrieve New

A.8.3 检索共享

A.8.3 Retrieve Shared

A.8.4 对象化程序

A.8.4 Objectifier

A.8.5 对象递归

A.8.5 Object Recursion

A.9 Gang of Four 模式定义

A.9 Gang of Four Pattern Definitions

A.9.1 抽象工厂

A.9.1 Abstract Factory

A.9.2 Factory 方法

A.9.2 Factory Method

A.9.3 装饰器

A.9.3 Decorator

A.9.4 代理

A.9.4 Proxy

A.9.5 责任链

A.9.5 Chain of Responsibility

A.9.6 模板方法

A.9.6 Template Method

书目

Bibliography

指数

Index

数字

Figures

2.1 装饰器的常用示例 UML。

2.1 Decorator’s usual example UML.

2.2 Objectifier 作为 UML。

2.2 Objectifier as UML.

2.3 作为 UML 的对象递归

2.3 Object Recursion as UML.

2.4 作为 UML 的简单方法调用。

2.4 A simple method call as UML.

2.5 方法调用的各个部分。

2.5 The parts of a method call.

2.6 一个简单的设计空间。

2.6 A simple design space.

2.7 带有 EDP 的简单设计空间。

2.7 A simple design space with EDPs.

2.8 我们的前四个 EDP。

2.8 Our first four EDPs.

2.9 设计空间扩展到三维空间。

2.9 The design space extended to three dimensions.

2.10 方法相似性固定为 similar 的设计空间。

2.10 The design space with method similarity fixed to similar.

2.11 递归示例 UML.

2.11 Recursion Example UML.

2.12 代理重定向示例 UML。

2.12 Deputized Redirection example UML.

3.1 UML 协作图。

3.1 UML collaboration diagram.

3.2 策略作为 UML 中的 pattern:role 标签。

3.2 Strategy as pattern:role tags in UML.

3.3 一个不那么庞大的系统的巨大 UML。

3.3 Huge UML of a not-so-huge system.

3.4 在 UML 中作为 pattern:role 标签的多个 Strategy 实例。

3.4 Multiple instances of Strategy as pattern:role tags in UML.

3.5 折叠的 PINbox。

3.5 Collapsed PINbox.

3.6 折叠的 PINbox 作为注释。

3.6 Collapsed PINbox as annotation.

3.7 类图中的单例抽象工厂

3.7 Singleton and Abstract Factory in class diagram.

3.8 序列图中的模板方法

3.8 Template Method in sequence diagram.

3.9 标准 PIN 码盒。

3.9 Standard PINbox.

3.10 与 UML 类图一起使用的 PIN。

3.10 PIN used with UML class diagram.

3.11 与 UML 序列图一起使用的 PIN。

3.11 PIN used with UML sequence diagram.

3.12 标准 PIN 角色连接。

3.12 Standard PIN role connections.

3.13 空白的扩展 PIN 实例。

3.13 Blank expanded PIN instance.

3.14 扩展的 PIN 实例。

3.14 Expanded PIN instance.

3.15 使用 UML 的扩展 PIN 实例。

3.15 Expanded PIN instance using UML.

3.16 需要多个相关的密码框。

3.16 A need for multiple related PINboxes.

3.17 堆叠 PINbox。

3.17 Stacked PINbox.

3.18 将多个 Strategy 实例作为 PIN 框。

3.18 Multiple Strategy instances as PINboxes.

3.19 显示多个 Strategy PINbox 之间的交互。

3.19 Showing the interaction between multiple Strategy PINboxes.

3.20 抽象工厂作为更大的 UML 图的一部分。

3.20 Abstract Factory as part of a larger UML diagram.

3.21 Abstract Factory 包含在扩展的 PIN 框中。

3.21 Abstract Factory subsumed within the expanded PINbox.

3.22 合并的 PINbox。

3.22 Coalesced PINbox.

4.1 将接口继承 EDP 抽象为 UML。

4.1 Abstract Interface and Inheritance EDPs as UML.

4.2 Fulfill 方法的内部定义为 UML。

4.2 Internal definition of Fulfill Method as UML.

4.3 将 Method 实现为简单的连接 PIN 码框。

4.3 Fulfill Method as simple connected PINboxes.

4.4 将 Fulfill Method 作为展开的 PINbox。

4.4 Fulfill Method as expanded PINbox.

4.5 将 Fulfill Method 作为标准 PINbox。

4.5 Fulfill Method as standard PINbox.

4.6 在 Fulfill Method 中翻转我们的 EDP—哎呀。

4.6 Flipping our EDPs in Fulfill Method—oops.

4.7 将 EDP 翻转为 PIN 框。

4.7 Flipped EDPs as PINboxes.

4.8 可以实现抽象接口 EDP 的替代类。

4.8 Alternative classes that can fulfill an Abstract Interface EDP.

4.9 可以满足继承 EDP 的替代结构。

4.9 Alternative structures that can fulfill an Inheritance EDP.

4.10 Decorator 的常用示例 UML。

4.10 Decorator’s usual example UML.

4.11 将方法定义实现为带注释的 UML。

4.11 Fulfill Method definition as annotated UML.

4.12 用 PIN 注释的 Objectifier UML。

4.12 Objectifier UML annotated with PIN.

4.13 ObjectifierTrusted Redirection

4.13 Objectifier and Trusted Redirection.

4.14 用 PIN 注释的对象递归

4.14 Object Recursion annotated with PIN.

4.15 对象递归仅作为 PIN。

4.15 Object Recursion as just PIN.

4.16 对象递归扩展方法

4.16 Object Recursion and Extend Method.

4.17 用 PIN 注释的装饰器

4.17 Decorator annotated with PIN.

4.18 Decorator 作为 PIN。

4.18 Decorator as PIN.

4.19 Decorator 实例作为 PINbox。

4.19 Decorator instance as a PINbox.

4.20 扩展 Decorator:一级。

4.20 Expanding Decorator: one level.

4.21 扩展 Decorator:两个级别。

4.21 Expanding Decorator: two levels.

4.22 扩展 Decorator:三个级别。

4.22 Expanding Decorator: three levels.

4.23 扩展 Decorator:四个级别。

4.23 Expanding Decorator: four levels.

4.24 重命名方法重构之前的委托

4.24 Delegation before Rename Method refactoring.

4.25 重命名方法重构后的委托 - 重定向

4.25 Delegation after Rename Method refactoring—Redirection.

4.26 Move 方法重构之前的委托

4.26 Delegation before Move Method refactoring.

4.27 方法相似性固定为非相似性的设计空间。

4.27 The design space with method similarity fixed to dissimilar.

4.28 Move Method 重构后的委托:无聊的情况。

4.28 Delegation after Move Method refactoring: boring case.

4.29 Move Method 重构后的委托:归为相同类型。

4.29 Delegation after Move Method refactoring: into same type.

4.30 Move 方法重构后的委托委托的 Conglomeration

4.30 Delegation after Move Method refactoring: Delegated Conglomeration.

4.31 Move 方法重构后的委托集合

4.31 Delegation after Move Method refactoring: Conglomeration.

4.32 Move 方法重构后的委托可信委托

4.32 Delegation after Move Method refactoring: Trusted Delegation.

4.33 Move Method 重构后的委托Revert Method

4.33 Delegation after Move Method refactoring: Revert Method.

4.34 Move 方法重构后的委托委托

4.34 Delegation after Move Method refactoring: Deputized Delegation.

4.35 总结到目前为止的重构效果。

4.35 Summarizing refactoring effects so far.

4.36 EDP 和选定的其他模式之间的隐式 used-by 关系。

4.36 Implicit used-by relationships among the EDPs and selected other patterns.

4.37 完整的方法调用 EDP 设计空间:不同方法。

4.37 The full method-call EDP design space: dissimilar method.

4.38 完整的方法调用 EDP 设计空间:类似的方法。

4.38 The full method-call EDP design space: similar method.

4.39 方法调用 EDP 重构关系。

4.39 Method-call EDP refactoring relations.

5.1 多态方法

5.1 Polymorphic approach

5.2 子类化方法

5.2 Subclassing approach

5.3 显示 Trusted Delegation 实例的 UI 类集群。

5.3 UI class cluster showing an instance of Trusted Delegation.

5.4 显示可信重定向实例的 UI 类集群。

5.4 UI class cluster showing an instance of Trusted Redirection.

5.5 UI 类集群显示 Deputized Delegation 的实例。

5.5 UI class cluster showing an instance of Deputized Delegation.

5.6 UI 类集群显示代理重定向的实例。

5.6 UI class cluster showing an instance of Deputized Redirection.

7.1 抽象工厂包含在扩展的 PIN 框中。

7.1 Abstract Factory subsumed within the expanded PINbox.

7.2 将图表简化为仅 Abstract Factory 的一个实例。

7.2 Reducing the diagram to just one instance of Abstract Factory.

7.3 简化图 7.2.

7.3 Simplifying Figure 7.2.

7.4 仅将 Abstract Factory 作为 PIN。

7.4 Abstract Factory as PIN only.

7.5 Factory Method 包含在展开的 PIN 码框中。

7.5 Factory Method subsumed within the expanded PINbox.

7.6 出厂方法仅作为 PIN。

7.6 Factory Method as PIN only.

7.7 Decorator 被包含在扩展的 PIN 码框中。

7.7 Decorator subsumed with the expanded PINbox.

7.8 仅将 Decorator 作为 PIN。

7.8 Decorator as PIN only.

7.9 Decorator 扩展了三层并展平。

7.9 Decorator expanded three levels deep and flattened.

7.10 代理被纳入扩展的 PIN 码框。

7.10 Proxy subsumed with the expanded PINbox.

7.11 仅代理为 PIN。

7.11 Proxy as PIN only.

7.12 重新组织了代理 PIN 以更好地匹配 Decorator

7.12 Proxy PIN reorganized to better match Decorator.

7.13 责任包含在扩展的密码箱中。

7.13 Chain of Responsibility subsumed within the expanded PINbox.

7.14 责任仅作为 PIN。

7.14 Chain of Responsibility as PIN only.

7.15 模板方法包含在展开的 PIN 码框中。

7.15 Template Method subsumed within the expanded PINbox.

7.16 模板方法简化为单个实例。

7.16 Template Method reduced to a single instance.

7.17 模板方法仅作为 PIN。

7.17 Template Method as PIN only.

7.18 重新组织了模板方法 PIN 以更好地匹配 Decorator

7.18 Template Method PIN reorganized to better match Decorator.

7.19 使用模板方法重新定义了工厂方法

7.19 Factory Method redefined using Template Method.

A.1 全方法调用 EDP 设计空间:类似方法。

A.1 The full method call EDP design space: similar method.

A.2 完整的方法称为 EDP 设计空间:不同方法。

A.2 The full method call EDP design space: dissimilar method.

A.3 标准密码盒

A.3 Standard PINbox

A.4 扩展 PIN 实例

A.4 Expanded PIN instance

Tables

2.1 样片分为样板定义的三类

2.1 Pattern pieces sorted into three categories of a pattern definition

2.2 面向对象编程的实体之间的所有交互

2.2 All interactions between entities of object-oriented programming

2.3 面向对象编程的实体之间的非作用域交互

2.3 Nonscoping interactions between entities of object-oriented programming

A.1 面向对象编程的实体之间的所有交互

A.1 All interactions between entities of object-oriented programming

A.2 面向对象编程的实体之间的非作用域交互

A.2 Nonscoping interactions between entities of object-oriented programming

清单

Listings

2.1 作为伪代码的简单方法调用。

2.1 A simple method call as pseudocode.

2.2 类、实例和命名空间中的字段,如 C++ 中定义和使用的字段。

2.2 Fields within classes, instances, and namespaces, as defined and used in C++.

2.3 一个 Java 类,以及一个可能的等效对象和类型。

2.3 A Java class, and one possible equivalent object and type.

2.4 键入作为上下文。

2.4 Typing as context.

2.5 作为伪代码的方法调用链。

2.5 A method call chain as pseudocode.

2.6 图 2.5 的简单方法调用。

2.6 Simple method call for Figure 2.5.

2.7 Java 中的递归方法调用示例。

2.7 Example of a Recursion method call in Java.

2.8 C++ 中的委托方法调用示例。

2.8 Example of a Delegation method call in C++.

2.9 Objective-C 中的 Redirection 方法调用示例。

2.9 Example of a Redirection method call in Objective-C.

2.10 Java 中的 Concorporationomeration 方法调用示例。

2.10 Example of a Conglomeration method call in Java.

5.1 未初始化的数据。

5.1 Uninitialized data.

5.2 固定默认值。

5.2 Fixed default values.

5.3 动态初始化

5.3 Dynamic initialization

5.4 创建对象实现。

5.4 Create Object Implementation.

5.5 使用更新检索

5.5 Retrieve with an update.

5.6 在临时变量中检索

5.6 Retrieve in a temporary variable.

5.7 Objective-C 中的基本继承示例。

5.7 Basic inheritance example in Objective-C.

5.8 覆盖实现。

5.8 Overriding an implementation.

5.9 实现假设不匹配。

5.9 Implementation assumption mismatch.

5.10 明显的修复 — 但可能不可行。

5.10 Obvious fix—but likely not feasible.

5.11 在保留旧代码的同时修复 bug。

5.11 Fixing a bug while leaving old code in place.

5.12 使用重定向来隐藏界面的一部分。

5.12 Using Redirection to hide part of an interface.

5.13 动物几乎都是移动的,但移动方式却截然不同。

5.13 Animals almost all move but in very different ways.

5.14 首席执行官将责任下放。

5.14 CEO delegates out responsibilities.

5.15 汤姆在帮忙粉刷栅栏。

5.15 Tom paints the fence with help.

5.16 准备工作和清理工作很重要。

5.16 Prep work and cleanup are important.

5.17 准备工作和清理是可分解的。

5.17 Prep work and cleanup are decomposable.

5.18 C++ 中用于协议回退的实例交换。

5.18 Instance swapping for protocol fallback in C++.

5.19 使用 Revert Method 自动回退/前进。

5.19 Auto fallback/forward using Revert Method.

5.20 在 Python 中使用重定向添加行为。

5.20 Using Redirection in Python to add behavior.

5.21 使用 Extend Method 添加行为。

5.21 Using Extend Method to add behavior.

5.22 在 Java 中天真地邀请朋友。

5.22 Inviting friends naively in Java.

5.23 邀请朋友的方法稍微好一些。

5.23 A slightly better approach for inviting friends.

5.24 Java 中的委托聚合

5.24 Delegated Conglomeration in Java.

5.25 C 语言中的传统迭代和调用。

5.25 Traditional iteration and invocation in C.

5.26 C++ 中面向对象的迭代和调用。

5.26 Object-oriented iteration and invocation in C++.

5.27 C++ 中的基本重定向递归

5.27 Basic Redirected Recursion in C++.

5.28 实现重定向递归的伞兵。

5.28 Paratroopers implementing Redirected Recursion.

5.29 演示 C++ 中可信委派的 UI 小部件。

5.29 UI widgets demonstrating Trusted Delegation in C++.

5.30 C++ 中的事件处理程序显示可信重定向

5.30 Event handler in C++ showing Trusted Redirection.

5.31 演示 C++ 中的代理委派的 UI 小部件。

5.31 UI widgets demonstrating Deputized Delegation in C++.

5.32 演示 C++ 中的代理重定向的 UI 小部件。

5.32 UI widgets demonstrating Deputized Redirection in C++.

6.1 用于选择行为的条件语句。

6.1 Conditionals to select behavior.

6.2 使用 Objectifier 选择行为。

6.2 Using Objectifier to select behavior.

A.1 简单代码示例

A.1 Simple code example

前言

Foreword

我想到了电影 2001:太空漫游 中有一个精彩的场景。

There’s a wonderful scene in the movie 2001: A Space Odyssey that comes to mind.

大卫·鲍曼博士在废弃的发现号飞船上独自度过了几个月——在早些时候对失控的哈尔进行了脑叶切除术之后——他接近了一块将他带入新世界的巨石。他返回地球的最后一条信息结束了:“到处都是星星!

Having spent several months alone on the derelict ship Discovery—and that after having earlier lobotomized the errant Hal—Dr. David Bowman approaches a monolith that draws him in to a new world. His final message back to earth ends “It’s full of stars!”

软件密集型系统是我们用自己的脑力劳动创造的新世界。鲍曼看到的世界是由原子形成的,因此充满了恒星,而我们的世界是由比特组成的......并且充满了模式。

Software-intensive systems are new worlds that we create with our own mental labor. Whereas the world that Bowman saw was formed from atoms and thus full of stars, our worlds are formed from bits...and are full of patterns.

无论有意与否,所有结构良好的软件密集型系统都充满了模式。识别系统中的模式有助于提高对该系统进行推理的抽象级别;在系统上强加模式有助于为该系统带来进一步的秩序、优雅和简单。根据我的经验,模式是过去二十年中软件工程领域最重要的发展之一。

Whether intentional or not, all well-structured, software-intensive systems are full of patterns. Identifying the patterns in a system serves to raise the level of abstraction in reasoning about that system; imposing patterns on a system serves to bring even further order, elegance, and simplicity to that system. In my experience, patterns are one of the most important developments in software engineering in the past two decades.

我有幸与 Jason 一起工作,见证了他在 SPQR 方面的工作,让我向你保证,他为模式的理解和实践的进步做出了巨大贡献。Elemental Design Patterns 将帮助您以一种新的方式思考模式,这种方式将帮助您应用模式来改进您创建和发展的软件世界。如果你是 patterns 的新手,这是一本开始你旅程的好书;如果你是个有模式的老手,那么我希望你会学到一些新东西。我当然知道。

I’ve had the pleasure of working with Jason as he evolved his work on SPQR, and let me assure you that he has contributed greatly to the advance of the understanding and practice of patterns. Elemental Design Patterns will help you think about patterns in a new way, a way that will help you apply patterns to improve the software worlds that you create and evolve. If you are new to patterns, this is a great book to start your journey; if you are an old hand with patterns, then I expect you’ll learn some new things. I certainly did.

Grady Booch

IBM 院士

2012 年 2 月

Grady Booch

IBM Fellow

February, 2012

前言

Preface

本书介绍了一类新的设计模式,即 Elemental Design Patterns,它构成了软件工程设计模式的学习和应用的基础。它的基础是对软件编程理论结构的研究,但它旨在兼顾实用和务实。它适用于初级程序员和经验丰富的开发人员。它应该帮助学生参与软件行业,并为研究人员提供新的思考要点。

This book is an introduction to a new class of design pattern, the Elemental Design Patterns, which form a foundation for the study and application of software engineering design patterns. Its foundations are in research into the very fabric of software programming theory, but it is intended to be practical and pragmatic. It is intended for both the beginning programmer and the seasoned developer. It should help students engage with the software industry and give researchers new points to ponder.

简而言之,这本书是用来使用的

In short, this book is meant to be used.

到最后,你的工具箱里应该有一套新的工具,对我们每天使用的一些编程基本概念有更丰富的理解,并了解它们如何相互关联和交互以做出令人惊叹的事情。基本设计模式(Elemental Design Patterns,简称 EDP)是我们反射性地使用的基本编程思想的集合,在这样做时可能不会三思而后行。这项工作为它们提供了明确的描述、在讨论中使用的规范化名称,以及一个协同使用它们并根据它们各自的优点进行比较的框架。如果你是一名新学生,你会了解到,与其将不断增长的设计模式文献视为令人生畏的全有或全无的块的集合,不如有机会逐个接受它们,并逐渐以有条不紊的方式理解这些文献。如果你是软件设计和模式的老手,你会找到新的方法来看待旧方法,并为我们的学科看到新的机会。

By the end of it, you should have a new set of tools in your toolbelt, a richer understanding of some of the basic concepts of programming that we all use every day, and knowledge of how they relate and interact with one another to do amazing things. The Elemental Design Patterns, or EDPs, are a collection of fundamental programming ideas that we use reflexively and probably don’t think twice about when doing so. This body of work gives them explicit descriptions, regularized names to use in discussions, and a framework for using them in concert and for comparing them on their own merits. If you’re a new student, you’ll learn that instead of facing the ever-growing design patterns literature as a collection of daunting all-or-nothing blocks, you have a chance to take them on piece by piece and gradually understand the literature in a methodical way. If you’re an old hand at software design and patterns, you’ll find new ways to look at old approaches and see new opportunities for our discipline.

本书假设您对设计模式这个领域有一定的了解,但尚未使用或详细研究过它们。知道它们的存在并对它们是什么有一个简短的口语知识就足以开始讨论。这本书并不假定你有编程理论、语言设计方面的背景,甚至没有面向对象编程方面的强大背景,只是希望学习如何批判性地思考软件设计。这些主题将被触及,但对于那些有兴趣通过提供的参考资料深入研究它们的人来说,这只是一个起点。统一建模语言用于描述小示例,如果您还不了解 UML,我建议将 [20] 或 [33] 作为参考。您应该具有编程的基本基础,无论是过程编程还是面向对象编程。后者会有所帮助,但并非绝对必要 — 本文提供了许多必要的信息,以易于理解的块形式解释面向对象的编程。在面向对象系统方面经验丰富的开发人员可能仍然会惊讶地发现,他们认为自己很久以前就已经掌握了这些概念有了新的视角,并且对面向对象编程作为一个整体有了更大的欣赏。

This book assumes you have a passing familiarity with design patterns as a field but have not used or studied them in detail. Knowing that they exist and having a brief colloquial knowledge of what they are is enough to start the discussion. The book does not assume you have a background in programming theory, language design, or even a strong one in object-oriented programming, just a desire to learn how to think critically about software design. These subjects will be touched on but only as a starting point for those interested in diving deeper into them through the provided references. The Unified Modeling Language is used to describe small examples, and I suggest either [20] or [33] as references if you do not already know UML. You should have a basic foundation in programming, either procedural or object oriented. The latter will help, but it’s not absolutely required—this text provides much of the necessary information to explain object-oriented programming in easily digestible chunks. Developers experienced with object-oriented systems may still be surprised at finding new perspectives on concepts that they thought they had mastered long ago and a greater appreciation for object-oriented programming as a whole.

许多程序员将“设计模式社区”视为一个深奥的专家团体,他们自己并不参与其中。通过为您提供关于什么可以构成设计模式的新视角,这本书应该让您相信每个程序员都是设计模式社区的成员,无论他们是否知道。每个程序员每次编写一行代码时都会使用设计模式,即使不要这样想。他们也不太可能意识到他们拥有的选择。设计模式是共享的概念空间,我们在其中书写塑造世界的电子梦想。是时候让我们有一张我们工作和娱乐的景观地图了。

Many programmers see the “design patterns community” as an esoteric body of experts and one that they themselves are not a part of. By giving you a new perspective on what can constitute a design pattern, this book should convince you that every programmer is a member of the design patterns community, whether they know it or not. Every single programmer uses design patterns every time they write a line of code, even if don’t think of it that way. Nor are they likely to realize the options they have at their disposal. Design patterns are the shared conceptual space in which we write the electronic dreams that shape our world. It’s time we had a map of the landscape in which we work and play.

以开创性的 Gang of Four 文本 [21] 为例,本书分为两个部分。首先是讨论为什么写这本书以及写这本书的目标读者,并解释什么是 EDP、它们来自哪里以及为什么它们很重要。本节解释了 EDP 背后的基本原理和原因。接下来是 Pattern Instance Notation 的介绍,这是一个图表系统,用于在多个粒度级别和多种环境中处理模式。第一部分的结尾是关于如何使用 EDP 来构建和结合更伟大的设计模式文献的讨论。本书的第二部分是设计模式的集合,从 EDP 开始,通过它们如何组合形成中间模式的示例,最后是重新铸造为 EDP 组合的 Gang of Four 模式的选择。这里介绍的 EDP 只是 EDP 目录的一部分,EDP 目录是第一轮已定义和描述的基本模式的集合。随着基本概念的扎根,软件工程社区将继续定义和完善其他 EDP。我希望您决定帮助这项工作。

Following the example of the seminal Gang of Four text [21], this book is divided into two sections. First is a discussion of why this book was written and who it is written for and an explanation of what EDPs are, where they came from, and why they’re important. This section explains the rationale, the why, behind the EDPs. Next is an introduction to the Pattern Instance Notation, a diagramming system for working with patterns at many levels of granularity and in a multitude of environments. Wrapping up this first section is a discussion of how EDPs can be used to build up to, and in conjunction with, the greater design patterns literature. The second section of the book is a collection of design patterns, starting with the EDPs and working through examples of how they combine to form Intermediate patterns, and finally, a selection of the Gang of Four patterns recast as EDP compositions. The EDPs presented here are only a portion of the EDP Catalog, a collection of the first round of defined and described fundamental patterns. The software engineering community will continue to define and refine additional EDPs as the underlying concepts take root. I hope you decide to help in the endeavor.

欢迎,很高兴您加入我们。

Welcome, it’s good to have you join us.

确认

Acknowledgments

我要感谢很多人让这本书栩栩如生。按时间顺序不完全......

I have many people to thank for this book coming to life. In not quite chronological order...

来自北卡罗来纳大学教堂山分校的 David Stotts,我的博士生导师,多年来一直监督 SPQR 和 EDP 的诞生;还有我的委员会,尽管他们确信这可能不可行,但认为这将是一次有趣的旅程,无论如何都让我去寻找铜戒指:Jan Prins、David Plaisted、Al Segars 和 Sid Chatterjee。你们每个人都在关键时刻提供了宝贵的帮助。

From the University of North Carolina at Chapel Hill, David Stotts, my Ph.D. advisor who oversaw the birth of SPQR and the EDPs over many years; also my committee, who, even though they were convinced it was probably infeasible, thought it would be an interesting journey and let me go for the brass ring anyway: Jan Prins, David Plaisted, Al Segars, and Sid Chatterjee. You each added invaluable help at critical times.

我在纽约的 IBM Watson Research 工作多年后,又是 Sid Chatterjee,他说服我来 Big Blue Playpen 玩;Clay Williams,他让我自由地进一步追求这些疯狂的想法,我仍然怀念和他一起喝咖啡;Peter Santhanam,他倡导这些想法,我从他那里学到了对遗留系统的更深刻的理解;Brent Hailpern,我从他那里学到了很多宝贵的经验,包括管理、公司生活的黑色幽默和简单的人性;伊迪丝·舍恩伯格(Edith Schonberg),她比任何经理都更能忍受我的恶作剧;还有许多其他听我疯狂地谈论这组作品的人,动不动就说。我的朋友们,我想念你们所有人。

From my years at IBM Watson Research in New York, Sid Chatterjee again, who convinced me to come play in the Big Blue Playpen; Clay Williams, who gave me free rein to pursue these crazy ideas further and with whom I still miss having coffee; Peter Santhanam, who championed those ideas and from whom I learned a greater appreciation for legacy systems; Brent Hailpern, from whom I learned many valuable lessons in management, the dark humor of corporate life and simple humanity; Edith Schonberg, who put up with my shenanigans more than any manager should have to; and many others who listened to me maniacally talk about this body of work at every turn. My friends, I miss you all.

同样来自 IBM,但值得特别提及的是 Grady Booch,他带我在他的羽翼下进行了一次疯狂的旅程,我不会用任何东西来交换。Grady,您的指导、指导和宣传是不可估量的,我期待着未来的合作和持续的友谊。

Also from IBM but deserving a special mention, Grady Booch, who took me under his wing for a wild ride that I wouldn’t have traded for anything. Grady, your guidance, mentoring, and advocacy have been immeasurable, and I look forward to future collaborations and continued friendship.

在华盛顿州柯克兰的 The Software Revolution, Inc.,我现在是高级计算机科学家,我必须感谢大家理解和支持我把这些信息写在纸上。与你们所有人一起工作真是一件愉快的事情,我渴望看到我们可以将公司带到哪里。

From The Software Revolution, Inc., in Kirkland, Washington, where I am now Senior Computer Scientist, I have to thank everyone for being understanding and supportive of my need to commit this information to paper. It has been a true pleasure working with all of you, and I am eager to see where we can take our company.

对于我的许多审稿人,您的建议和评论非常有见地和帮助。您使这本书成为更好的产品,我向您致以最深的感谢:Lee Ackerman、Lars Bishop、Robert Bogetti、Robert Couch、Bernard Farrell、Mary Lou Hines Fritts、Gail Murphy、Jeffrey Overbey、Ethan Roberts、Carlota Sage、Davie Sweis、Peri Tarr 和 Rebecca Wirfs-Brock。Addison-Wesley 的 Elizabeth Ryan、Raina Chrobak、Chris Zahn 和 Chris Guzikowski 是这个过程试验期间富有同情心的支持的典范——我感谢你和在场的其他工作人员,特别感谢 Carol Lallier,她对这本书的专业润色非常宝贵。

To my many reviewers, your advice and comments were highly insightful and helpful. You made this book a much better product, and you have my deepest thanks: Lee Ackerman, Lars Bishop, Robert Bogetti, Robert Couch, Bernard Farrell, Mary Lou Hines Fritts, Gail Murphy, Jeffrey Overbey, Ethan Roberts, Carlota Sage, Davie Sweis, Peri Tarr, and Rebecca Wirfs-Brock. Elizabeth Ryan, Raina Chrobak, Chris Zahn, and Chris Guzikowski at Addison-Wesley were the model of compassionate support during the trials of this process—my thanks to you and the rest of the crew there, with a special thanks to Carol Lallier, whose expert polish on this book was invaluable.

就个人而言,我感谢我的朋友和家人,他们非常耐心,而我为此投入了似乎无尽的时间,尽管他们希望在我已经搬回西雅图地区后能看到更多的我。

On a personal note, I thank my friends and family, who have been incredibly patient while I have put in seemingly endless hours on this, even though they were hoping to see more of me now that I’ve moved back to the Seattle area.

最后是我的妻子 Leah。在我们在一起的这段时间里,您在许多大大小小的方面都支持了我。你付出了你的时间、你的耐心和你的爱,你得到了我巨大的爱和感激。谢谢。言语根本不够用。

Finally, my wife Leah. You have supported me in so many large and small ways throughout our time together. You have given your time, your patience, and your love, and you have my immense love and gratitude. Thank you. Words are simply inadequate.

谢谢大家。你们每个人都以某种方式为这些想法和本文的完善做出了贡献。这可能是我的孩子,但它有很多助产士。

Thank you all. Every one of you contributed in some way to the refinement of these ideas and this text. This may have been my baby, but it had many midwives.

— Jason McC. Smith

西雅图
2011
年 9 月 4 日

— Jason McC. Smith

Seattle

September 4, 2011

关于作者

About the Author

Jason McC. Smith 于 2005 年从北卡罗来纳大学教堂山分校获得计算机科学博士学位,元素设计模式作为模式查询和识别系统项目的一部分诞生于该大学。Smith 博士在 UNC-CH 进行的研究获得了两项美国专利,一项是与 SPQR 相关的技术,另一项是 FaceTop 分布式文档协作系统。

Jason McC. Smith received his Ph.D. in computer science in 2005 from the University of North Carolina at Chapel Hill, where the Elemental Design Patterns were born as part of the System for Pattern Query and Recognition project. Dr. Smith has been awarded two U.S. patents for research performed at UNC-CH, one for technologies related to SPQR and one for the FaceTop distributed document collaboration system.

在此之前,Smith 博士在工业界工作多年,担任物理仿真工程师和顾问,拥有华盛顿大学物理学和数学双 B.Sc 学位。值得注意的项目包括声纳和海洋环境模拟、电子工程模拟、商用和军用飞机飞行模拟以及实时图形训练系统。

Prior to that, Dr. Smith spent many years in industry as a physics simulation engineer and consultant building off of dual B.Sc. degrees in physics and mathematics from the University of Washington. Projects of note included sonar and oceanic environment simulation, electronic engineering simulation, commercial and military aircraft flight simulation, and real-time graphical training systems.

在 IBM Watson Research 的四年时间为 Smith 博士提供了一个机会,可以将 SPQR 和 EDP 目录和组合方法的经验应用于大量软件,包括传统和现代软件。

Four years at IBM Watson Research provided Dr. Smith with an opportunity to apply the lessons of SPQR and the EDP catalog and compositional approach to immense bodies of software, both legacy and modern.

Smith 博士目前是位于华盛顿州柯克兰的 The Software Revolution, Inc. 的高级研究科学家,在那里他继续完善 EDP 目录,并寻找增强公司实现自动化现代化和旧系统转型目标的方法。

Dr. Smith is currently Senior Research Scientist at The Software Revolution, Inc., in Kirkland, Washington, where he continues to refine the EDP catalog and look for ways to enhance the company’s goal of automated modernization and transformation of legacy systems.

1. 设计模式简介

1. Introduction to Design Patterns

无论以何种标准衡量,设计模式都是软件工程中最成功的进步之一。不过,设计模式的历史是一段奇怪的历史,在此过程中的某个时候,它们最初的实用性和优雅大部分已经被遗忘、放错地方,或者干脆被误解了。这本书可以为那些有设计模式经验的人填补一些可能的空白,并且可以为刚接触文学的学生提供一种更好的方式来一口一口地阅读它。归根结底,设计模式文献是相当大的信息块的集合,这些信息具有不同程度的可消化性。本文是一个基础,它为熟悉设计模式的从业者提供了一种将这些金块放入更大的理解系统中的方法,并为设计模式的新手提供了一种从基本原则和单独有意义的小部分学习它们的方法。基本设计模式是真正的基本模式,因为它们构成了设计模式作为一门学科的基础。

Design patterns are one of the most successful advances in software engineering, by any measure. The history of design patterns is a strange one though, and somewhere along the way, much of their original utility and elegance has been forgotten, misplaced, or simply miscommunicated. This book can fill in some possible gaps for those who have experience with design patterns and can provide students new to the literature a better way of consuming it bite by bite. When it comes down to it, the design patterns literature as it stands is a collection of rather large nuggets of information of varying degrees of digestibility. This text is a foundation that provides a practitioner familiar with design patterns a methodology for placing those nuggets into a larger system of understanding and provides the student new to design patterns an approach for learning them from basic principles and in smaller pieces that make sense individually. The Elemental Design Patterns are truly elemental in that they form a foundation for design patterns as a discipline.

软件工程社区的集体智慧是我们最宝贵的资产之一,我们还有很多东西可以相互学习。这本书及其所基于的研究试图揭示我们在设计模式方面所丢失的一些东西。在此过程中,它通过建立更好的模式共享讨论机制来帮助实现设计模式的初衷,让我们对我们生产和使用的软件有更丰富的理解。我们的社区已经产生了广泛的设计模式,但我们缺乏的是深度。也就是说,我们对广泛的领域有广泛的理解,但将它们拼接成一个综合整体的能力很弱。这让我想起了从炼金术到现代化学的历史过渡——在元素周期表出现之前,许多聪明的研究人员的集体智慧是精确的,但不是很强的相关性。可以说,德米特里·门捷列夫 (Dmitri Mendeleev) 的原始元素周期表的最大影响不在于它为化学家提供了一种识别物质构建块之间模式的方法,而是它提供了一种使用这些模式来预测当时未被发现的元素特性的方法。镓和锗是最早的例子,门捷列夫在发现它们之前就准确地描述了它们的化学和物理性质。元素周期表将化学从描述性学科推进到预测科学。

The collective wisdom of the software engineering community is one of our most valuable assets, and we still have much to learn from each other. This book and the research on which it is based are an attempt to bring to light some of what we have lost regarding design patterns. In the process, it helps fulfill the original intent of design patterns by establishing a better mechanism for shared discussions of patterns, giving us a richer understanding of the software we produce and consume. Our community has produced a breadth of design patterns, but what we lack is depth. That is, we have a broad understanding of wide areas but only a weak ability to stitch them together into a comprehensive whole. It reminds me of the historical transition from alchemy to modern chemistry—until the periodic table came along, the collective wisdom of many intelligent researchers was precise but not strongly correlated. Arguably, the biggest impact of Dmitri Mendeleev’s original periodic table was not so much that it provided a way for chemists to identify patterns between the building blocks of matter but that it provided a way to use those patterns to predict properties of then-undiscovered elements. Gallium and germanium were the first examples of this, with Mendeleev accurately describing their chemical and physical properties well before their discovery. The periodic table advanced chemistry from descriptive discipline to predictive science.

软件工程社区中设计模式的出现始于 1995 年开创性的 Design Patterns: Elements of Reusable Object-Oriented Software 的出版。四人帮(或 GoF)Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 收集了在 Gamma 1991 年博士论文之后渗透在研究和学术界的各种智慧。这部作品在很大程度上借鉴了克里斯托弗·亚历山大 (Christopher Alexander) 在 1960 年代的早期作品。Alexander 是一名土木工程师和建筑师,他的工作重点是寻找在特定背景下解决特定力组的解决方案模式。他的主要见解是,建筑中存在两种类型的设计,他称之为无意识无意识

The emergence of design patterns within the software engineering community began with the publication of the seminal Design Patterns: Elements of Reusable Object-Oriented Software in 1995. The Gang of Four (or GoF), Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, gathered the various collected wisdoms that had been percolating in the research and academic communities following Gamma’s 1991 PhD dissertation. That work drew heavily from Christopher Alexander’s earlier work in the 1960s. Alexander was a civil engineer and architect, and his work focused on finding patterns of solutions to particular sets of forces within particular contexts. His primary insight was that there are two types of design that occur within architecture, what he named unselfconscious and selfconscious.

无意识的设计最常见于所谓的原始文化中:每次都忠实地复制房屋设计,并使用学徒制来确保对特定设计的忠实度和忠实度。这种设计很少改变,坚持给定的形式被认为是目标,主要是因为特定的设计是几个世纪或几千年反复试验的精华——它有效,如果它没有坏,就不要修理它。虽然为人们提供住房的问题是普遍存在的,但发生该问题的各种环境,例如雨水、沙漠、冰、沼泽和森林,产生了一系列令人惊叹的风格和设计,但在特定环境中,单一设计可能被认为是“唯一的解决方案”,并且考虑到其特定的环境需求,它通常非常有效。然而,该设计在没有太多(如果有的话)个人自由裁量权或决策的情况下被应用。

Unselfconscious design is most often seen in so-called primitive cultures: a house design is copied faithfully, every time, and apprenticeships are used to ensure fidelity and faithfulness to that particular design. This design changes rarely, and adherence to a given form is considered the goal, primarily because the particular design is the distillation of centuries or millennia of trial and error—it works, and if it ain’t broke, don’t fix it. While the problem of providing people with housing is universal, the various contexts in which that problem occurs, such as rain, desert, ice, swamp, and forest, give rise to an amazing array of styles and designs, but within a particular context, a single design may be considered “the only solution,” and it is frequently incredibly effective given its specific environmental needs. The design, however, is applied without much, if any, individual discretion or decision making.

自我意识设计是一项更现代的发明;设计师几乎可以在涉及风格、美学和材料的每一个转折点上自由做出有意识的决定。这种建筑自由可以在给定地区、城市或社区内的各种现代建筑中看到。即使在您自己的街道上,您也可能会看到大量的风格和独特的装饰,每一种都是建筑师许多有意识的决定的结果。现代设计师有多种款式可供选择,一般来说,唯一的问题是平衡业主的审美和钱包。当然,这些房屋符合在居住者头上盖上屋顶的基本标准,但当有这么多其他轴线可以检查设计时,这是一个相当低的门槛。当设计师可以自由地做任何事情时,从几乎无限的不适当或纯粹糟糕的解决方案中挑选出有效的解决方案就变得更加困难。建筑规范是我们试图限制住房设计中错误选择的一种方式,但即使以这些为起点,这项任务也是一项艰巨的任务。仅仅阅读建筑规范并遵守它们不会产生有效的建筑作品。建筑规范是通用的,但好的建筑会考虑环境的每一个细节层次,从全球纬度和区域天气模式到当地土壤等级和特定场地的地形或树叶。

Selfconscious design is a more modern invention; the designer is free to make conscious decisions at almost every turn involving style, aesthetics, and materials. This architectural freedom can be seen in the wide array of modern architecture within a given locale, city, or neighborhood. Even on your own street, you are likely to see a plethora of styles and distinctive flourishes, each the result of many conscious decisions on the part of the architect. The modern designer has a wide palette of styles to choose from, and generally speaking, the only problem is balancing the owner’s aesthetics and wallet. Sure, the houses meet the basic criteria of putting a roof over the occupants’ heads, but that’s a pretty low bar to set when there are so many other axes on which a design can be examined. When a designer is freed to do anything, it becomes even harder to pick the effective solutions out of the nearly infinite set of inappropriate or just plain bad ones. Building codes are one way we try to limit the bad choices in housing design, but even given those as a starting point, the task is daunting. Merely reading building codes and adhering to them is not going to produce an effective work of architecture. Building codes are generic, but good architecture takes into consideration the environment at every level of detail, from global latitude and regional weather patterns to local soil grades and site-specific terrain or foliage.

您几乎可以在任何城镇或城市看到这种自我意识设计的结果。一栋房子可能是格鲁吉亚风格的,一座是伪维多利亚式的,另一栋是现代玻璃和钢制的盒子,或者可能是错层式、牧场或任何其他数量的风格、种类和类型的建筑、材料和建筑。然而,我们必须问自己,这些设计是否是针对特定环境、特定环境的最佳解决方案,甚至只是有效的解决方案。例如,德克萨斯州的奥斯汀可能不是建造无遮阳玻璃面建筑的最佳地点,因为夏季阳光非常强烈,冷却成本的大幅增加会带来额外的费用。纽约州北部可能不适合建造平屋顶,因为在严冬时节,数英尺厚的雪的重量会给房间的横梁增加很大的负荷。环境背景,即造成必须解决设计适当住房的一般问题的局面的一系列力量,经常被忽视,而解决方案通常只能最低限度地令人满意或产生必须解决的新问题。

You can see the results of this selfconscious design in almost any town or city. One house may be Georgian, one pseudo-Victorian, another a modern glass and steel box, or perhaps a split-level, a ranch, or any other number of styles, kinds, and types of construction, materials, and architectures. We have to ask ourselves, however, whether these designs work as optimal, or even just effective, solutions for that particular environment, for that particular context. Austin, Texas, for example, may not be the best place to build an unshaded glass-faced edifice because the sun is so intense in the summer, creating an added expense from the large increase in cooling costs. Upstate New York may not be an appropriate place for a flat roof because the weight of many feet of snow in the dead of winter adds a significant load to the room beams. The environmental context, the set of forces that create the situation in which the general problem of designing appropriate housing must be solved, is frequently ignored, and the solutions are generally only minimally satisfying or give rise to new problems that must be addressed.

这如何应用于软件工程应该很明显:我们几乎能够做任何突然出现在我们脑海中的事情,甚至比物理建筑的建筑师还要强大。这就是编程的惊人优势,也是它的致命弱点。我们几乎可以做任何事情,而且通常都能做到,但不幸的是,任何事情中好的子集都非常小,我们的项目经常延迟、超出预算,并且经常以壮观和安静的方式失败。我们很少带着成就感离开项目——更多时候,我们觉得我们躲过了一颗子弹。再。为什么会这样?为什么,当我们拥有数十年的集体经验,而且很可能在该领域拥有数百万年的统计经验时,我们每次处理问题时仍然在杂草中挣扎?一些设计师和开发人员似乎能够避开复杂性,并在设计中找到有效性的核心。我们其他人似乎永远被困在“因为我被告知要做”的无意识和自我意识设计的麻痹之间。

It should be apparent how this applies to software engineering: we are capable of doing nearly anything that pops into our heads, even more so than the architects of physical buildings. This is the amazing strength of programming—and its Achilles heel. We can do just about anything, and usually manage to do so, but unfortunately, the subset of good things out of the set of anything is quite small, and our projects are often late, over budget, and frequently fail in ways spectacular and quiet. Rarely do we walk away from projects with a feeling of accomplishment—more often, we feel we dodged a bullet. Again. Why is this? Why, when we have decades of collective experience, and quite possibly millions of tallied person-years in the field, are we still thrashing in the weeds every time we approach a problem? Some designers and developers seem to be phenomenally able to sidestep the complexity and find the kernel of effectiveness in a design. The rest of us seem to be perpetually stuck between the unselfconsciousness of “because I was told to” and the paralysis of selfconscious design.

亚历山大的工作试图缓解建筑建筑师的这个问题,以揭示原始文化在设计上的有效性与现代建筑近乎痉挛的尝试任何方法之间的差距。介于两者之间的某个地方是需要取得的平衡。我们需要找到存在于无意识架构中的基本原则和一般解决方案,并以一种使它们适用于各种环境的方式进行描述,这是有意识的和有意识的。通过反复试验来之不易的各种解决方案尝试的智慧需要提炼成任何人都可以学习的概念体系,应用于许多地方,并用作思考设计的指南。

Alexander’s work was an attempt to alleviate this problem for architects of buildings, to bring to light the disparity between the effectiveness of the primitive cultures at design and the nearly spastic try-anything approach of modern architecture. Somewhere in between is a balance to be struck. We need to find the underlying principles and general solutions that exist in unselfconscious architecture and describe them in a way that makes them applicable in a wide variety of contexts selfconsciously and with deliberate intent. The wisdom of the various attempts at solutions, hard-earned through trial and error, need to be distilled into a body of concepts that can be learned by anyone, applied in numerous places, and used as a guide for thinking about design.

这就是设计模式——由一个充满活力和强大的社区提炼的专业知识。这是最好的众包。自最初的 GoF 工作以来,十多年来,模式社区已经发展壮大且充满活力,我们的产出也非常庞大。Grady Booch 和 Celso Gonzalez 一直在他们的网站上收集他们在工业界和学术界能找到的所有模式 [11]。到目前为止,他们有 2,000 多个。这个社区的输出数量是巨大的,虽然有一些关于质量的讨论,但更紧迫的问题是规模问题。

This is what design patterns are—the distillation of expertise by an exuberant and robust community. This is crowdsourcing at its best. The patterns community that has grown over the decade-plus since the original GoF work is large and energetic, and our output is voluminous. Grady Booch and Celso Gonzalez have been collecting every pattern they can find in industry and academia at their website [11]. So far, they have over 2,000 of them. The quantity of output in this community is huge, and although there are some discussions about the quality, the more pressing problem is one of scale.

即使拥有完全索引、精心策划的高质量设计模式集合,对于非学者来说,也存在太多信息,无法准确快速地筛选。更糟糕的是,对于希望学习优秀设计背后的原则的学生来说,仅仅从优秀设计的例子中学习是非常困难的。这有点像试图通过在跑道上检查飞机来学习航空流的数学。对于认为自己已经发现了新设计模式的经验丰富的模式实践者来说,没有现成的方法可以将新模式与现有模式进行比较,以了解它与已建立的文献有何关系,也没有办法创建工具来支持这种需求。

Even with a fully indexed, well-curated collection of quality design patterns, there is simply too much information for a nonscholar to sift through accurately and quickly. Worse, it is incredibly difficult for a student wishing to learn the principles behind good design to do so solely from examples of good design. It is a bit like trying to learn the mathematics of aeronautical flow from inspecting aircraft on a runway. For experienced patterns practitioners who believe they have uncovered a new design pattern, there’s no ready way to compare a new pattern against existing patterns to see how it relates to the established literature, and there’s no way to create tooling to support this need.

软件开发社区需要的是更透彻地了解它所拥有的东西,一种方法可以解释如何更精确地描述现有的设计模式,并使用学生或新开发人员可以访问的组件和定义明确的原则来实现这一点。我们需要的是一份关于我们的设计模式文献的基本原则的指南,这样我们就可以更好地理解、教授和学习我们确定的最佳实践。这本书是该指南的基础。

What the software development community needs is a more thorough understanding of what it has at its disposal, a methodology that explains how to more precisely describe the existing design patterns and does so using components and well-defined principles that are accessible to the student or new developer. What we need is a guide to the underlying basic principles of our design patterns literature so that we can better comprehend, teach, and learn our identified best practices. This book is a foundation for that guide.

1.1. 部落沉思

1.1. Tribal Musings

我们从记录和传递已知最佳实践中获得的效率很重要,但我们必须这样做的原因在我们的社区中基本上被忽视了。说白了,我们是凡人,我们年轻的领域正在老龄化。我们已经失去了许多为我们行业奠定基础的杰出人物,还有更多人很快就会离开。这只是生活中的一个事实,我们没有做好充分的准备来作为一门学科来处理。

The efficiencies we gain from documenting and passing along known best practices are important, but the reason we must do so has been largely ignored in our community. To put it bluntly, we are mortal, and our young field is aging. Already we have lost a number of luminaries who established the groundwork for our industry, and many more will be gone soon. It is just a fact of life, one that we are poorly prepared to deal with as a discipline.

更糟糕的是,软件有一个奇特的特征,即寿命远远超过其预期的寿命。COBOL 仍然是全球业务系统中不可忽视的力量。Fortran 仍然在世界上的科学建模软件中执行大部分计算。目前,主要的高性能计算机系统在其固件深处嵌入了代码,这些代码最初是在三十年前或更长时间前以汇编程序或 C 语言创建的。您几乎可以肯定,在您最近购买的个人计算机附带的数百万行实现代码中,有一段源代码目前没有人理解。

Worse still, software has a peculiar trait of living long past its expected lifetime. COBOL is still a force to be reckoned with in business systems around the globe. Fortran still performs much of the computation in the world’s scientific modeling software. Currently shipping major high-performance computer systems have code embedded deep in their firmware that was first created three decades or more ago, in assembler or C. You can be almost certain that somewhere in the millions of lines of implementation that came with your latest personal computer acquisition lies a piece of source code that no person currently understands.

我们知道我们应该记录我们的软件;我们知道我们应该保持最新状态;我们知道我们应该致力于 Pen or Screen 为什么、如何和原因;但我们也知道这是一种痛苦。确实如此,所以我们不这样做。相反,我们拥有的是一个锁在开发人员脑海中的知识体系,这些知识在提示时断断续续地传递,并且仅在必要时传递,通常利益相关者之间没有任何共同理解的全面框架。

We know we should document our software; we know we should keep it up to date; we know we should commit to pen or screen the whys, the hows, and the reasons; but we also know it is a pain. It really is, so we don’t do it. What we have instead is a body of knowledge that is locked within the heads of developers, that is passed along in fits and spurts, when prompted and only where necessary, frequently without any comprehensive framework of common understanding among stakeholders.

格雷迪·布赫 (Grady Booch) 将“部落知识”一词推广开来 [10],它非常合适。它也有一些相当令人不安的推论。

Grady Booch has popularized the phrase “tribal knowledge” for such information [10], and it fits all too well. It also has some rather unsettling corollaries.

仅依靠口头传统来传递知识的文化在带宽和准确性方面都是有限的,这是假设他们有传递信息的强大传统。在信息传递的真实性和精确性方面纪律薄弱的文化会让自己更容易遭受更迅速的腐败。然而,强大的口头传统可能会导致非常不同的结果。

Cultures that rely solely on oral tradition for the passing of knowledge are limited in both bandwidth and accuracy, and that’s assuming they have a strong tradition of passing along the information. Cultures with a weak discipline for veracity and precision in information transfer leave themselves open to more rapid corruption. A strong oral tradition, however, can result in a very different outcome.

开发社区最终拥有信息传输的口头传统。尽管我们可能会写下我们理解的零碎内容,但我们通常不会写下我们的全部理解,而且我们也不会使此类文档与我们系统的发展保持同步。这种文档腐烂无处不在,只有通过四处询问更多信息,我们才有希望填补空白,找出为什么某个特定系统是这样的。

The development community has what is ultimately an oral tradition of information transfer. Although we may write down bits and pieces of what we understand, we frequently do not write down the entirety of our comprehension, and we do not keep such documentation in sync with the evolution of our systems. This document rot is pervasive, and only by asking around for further information can we hope to fill in the gaps to find out why a particular system is how it is.

老实说,这并不总是被视为一件坏事。敏捷软件开发方法更喜欢工作代码而不是文档化代码,很难反驳这种观点。当然,除非它很重要。敏捷系统以一种有趣的方式成为遗留系统,成长为成熟的代码库,拥有必须协同工作的大型团队。最终,如果成功,以敏捷开发方式开始的代码将面临许多与传统开发的系统相同的挑战。开发人员离开。文档腐烂。知识丢失了。

This isn’t always seen as a bad thing, to be honest. Agile software development methodologies prefer working code over documented code, and it’s hard to argue with this viewpoint. Until it matters, of course. Agile systems have a funny way of becoming legacy systems, of growing into mature codebases with larger teams that must work in concert. Eventually, code that started as an agile effort, if it is successful, will face many of the same challenges as traditionally developed systems. Developers leave. Documentation rots. Knowledge is lost.

目前的软件并不是任何人都可以准确地称为自文档化的,直接从源代码中提取以特定方式完成某件事的突出原因,对于自动化系统来说几乎是不可能的。这很遗憾,因为我们既想吃我们的蛋糕,也想吃它。我们希望在需要的时间和地点获得最新的文档,但我们不想否则就背负重担。我们希望我们的代码更加自文档化,或者至少是自动可文档化的,但我们大多数人都没有这种奢侈。所以我们弃踢并希望最好。与此同时,我们对系统的集体理解正在退化。最后,我们所拥有的最好被描述为一种非常薄弱的口头传统。

Software as it currently stands is not what anyone could accurately call self-documenting, and extracting the salient reasons why a thing was done in a particular way, directly from the source code, has been considered nearly impossible for an automated system. This is unfortunate, because we would like to have our cake and eat it too. We want up-to-date documentation when and where we need it, but we don’t want to be burdened with it otherwise. We’d like our code to be much more self-documenting, or at least automatically documentable, but most of us don’t have that luxury. So we punt and hope for the best. Meanwhile, our collective understanding of the system degrades. In the end, what we have is best described as a very weak oral tradition.

结果是收集到的部落知识退化为“部落神话”。“为什么”不是一个可以再回答的问题,只能说,“因为我们一直都是这样做的。我暗自怀疑,如果你曾经是开发团队的新员工,你现在正在惊恐地点头。你在现实生活中有过这样的讨论,可能比你记得的次数多。

The result is that the collected tribal knowledge degrades into “tribal mythology.” “Why?” is not a question that can be answered any longer, except to say, “Because we’ve always done it that way.” I have a sneaking suspicion that if you have ever been the new hire on a development team, you’re nodding in horror right now. You’ve had that discussion in real life, probably more times than you care to recall.

部落神话是没有理解的行动。这是死记硬背的,没有任何基础来说明原因。部落神话在一个群体中活跃的其他迹象包括:“因为这就是我被教导的方式。“我不太确定,但乔说这就是它的做法。”“Jane 本可以告诉你的,但她去年退休了,所以就复制那里的东西吧。”“哦,不,别改了!它会坏掉,我们无法修复它。这些评论表现出未能理解行为背后的原因,或者至少不愿意或无法将理解传递给听众。随着时间的推移,这种缺乏理解会滋生大量的不确定性和对变化的恐惧。不幸的是,在某种程度上,大多数项目都是现状,这具有讽刺意味,因为我们的行业是由创新、变革和最先进的进步驱动的。

Tribal mythology is action without comprehension. It is rote without any foundation on which to state why. Other indications that tribal mythology is active in a group include the following: “Because that’s how I was taught it.” “I’m not really sure, but Joe says that’s how its done.” “Jane could have told you, but she retired last year, so just copy what’s there.” “Oh no, don’t change that! It’ll break and we won’t be able to fix it.” These comments exhibit a failure to comprehend the reasons behind an action, or at least an unwillingness or inability to pass the comprehension along to the listener. Over time, this lack of understanding breeds a great deal of uncertainty and fear of change. Unfortunately, it is at some level the status quo on most projects, which is ironic given that our industry is driven by innovation, change, and advancement of the state of the art.

然而,部落智慧是这个部落神话的美德另一面。它是带有理解、如何伴随着原因的规定行动,并适应新环境、新情况和新问题。它超越了死记硬背,通过全面讨论行动背后的原因来提供启发。在过去的某个时候,对于系统中的几乎每一个行动或决策,都有人知道为什么会这样。推进这些决策,即使是很小的决策,也可能至关重要。小决策会累积到大型系统中,小设计会构建到大型设计中。通过确保我们拥有促进理解的强大知识保留传统,我们建立了部落智慧的传统。

Tribal wisdom, however, is the virtuous flip side of this tribal mythology. It is prescribed action with understanding, how accompanied by why, and is adaptable to new environments, new situations, and new problems. It transcends rote copying, and provides illumination through a comprehensive discussion of the reasons behind the action. At some point in the past, for almost every action or decision in a system, someone knew why it was done that way. Carrying those decisions forward, even the small ones, can be critical. Small decisions accrete into large systems, and small designs build into large designs. By ensuring that we have a strong tradition of knowledge retention that facilitates understanding, we build a tradition of tribal wisdom.

部落智慧是设计模式旨在收集的东西。可悲的是,他们经常被(误)当作部落神话,在没有清楚地理解原因的情况下应用了如何

Tribal wisdom is what design patterns were intended to collect. Sadly, they are frequently (mis)treated as tribal mythology, by applying the how without a clear comprehension of the why.

如果你还没有在你的职业生涯中遇到过这种情况,让我提供另一个可能具有启发性的例子。最近,我和妻子买了我们的第一套房子,随之而来的是我们的第一个院子。我们居住的地区以其雨水和苔藓而闻名。现在,我喜欢苔藓。它是绿色的,几乎不需要维护,而且是一种很好的柔软地面覆盖物。它满足了院子的所有通常要求,工作量比草地少,但我们遇到了一个奇怪的情况。院子的一部分被严重遮蔽,很少看到阳光。这个区域基本上是固体苔藓,没有草或任何其他植被。即使是耐阴的草也无法获得足够的光照来茁壮成长。

If you haven’t yet had the pleasure of running into this situation in your career, let me offer another example that may be illuminating. Recently my wife and I bought our first house, and with it, our first yard. The region we live in is renowned for its rain and consequently its moss. Now, I like moss. It’s green, it takes about zero maintenance, and it makes a nice soft ground cover. It satisfies all the usual requirements for a yard, with less work than grass requires, but we had an odd situation. Part of the yard is heavily shaded and rarely, if ever, sees sun. This area is basically solid moss, with no grass or any other vegetation. Even shade-tolerant grasses can’t get enough light to thrive.

然而,在重分的两侧各 20 英尺处,在太阳真正出来的大多数日子里都可以获得阳光。苔藓在本节中成片生长,但在我的初步评估中,我认为这很好。苔藓和草很好地共存,苔藓并没有把草呛死,只是填满了草不是那么厚的地方。在阳光最充足的地区,几乎没有苔藓,但有很多草。在最阴凉的地方,有固体苔藓,但没有草。在过渡地区,两者共存。还有什么比这更好的呢?

Twenty feet on either side of the heavily shaded portion, however, sunlight is available on most days when the sun is actually out. Moss grows in patches through this section, but in my initial assessment, I thought it was fine. The moss and grass were coexisting nicely, and the moss wasn’t choking out the grass, merely filling in the places where the grass wasn’t quite so thick. In the sunniest areas, there was almost no moss but lots of grass. In the shadiest areas, there was solid moss but no grass. In the transitional regions, the two coexisted. What could be better?

不幸的是,看到这种情况的长期居民感到震惊。“你得把苔藓都赶走!”当我问为什么时,我得到的回答是“因为它不是草”。“因为这就是你的工作。”“因为这对草坪不利。”没有人能告诉我,令我满意,为什么我应该摆脱这些苔藓。在我看来,如果我去除所有区域的所有苔藓,无论当地的微环境如何,我都会有一个光秃秃的地方,草不会在阴凉处生长。这并不是最佳选择。

Unfortunately, long-time residents who saw this situation were horrified. “You have to get rid of all the moss!” When I asked why, I was met with answers such as “Because it’s not grass.” “Because it’s what you do.” “Because it’s bad for the lawn.” No one could tell me, to my satisfaction, why I should get rid of the moss. It seemed to me that if I removed all of the moss, in all areas, regardless of the local micro-environment, I would have a bare spot where grass wouldn’t grow in the shade. This was less than optimal.

更糟糕的是,就像许多软件项目一样,我继承了这样一种情况,即我不知道以前的居民为院子做了什么维护,也不知道为什么。没有文件表明我应该为我的草坪做什么,或者为什么院子被保留为这种配置。所以我做了几个实验。在最阴凉的地方,我拔掉了一小段苔藓,并在上面种上了草。在院子的其他地方,我让苔藓去看看会发生什么。

To make matters worse, as is the case in many software projects, I had inherited a situation in which I had no idea what the previous residents had done for maintenance in the yard or why. There was no documentation to indicate what I should do for my lawn or why the yard had been left in this configuration. So I ran a couple of experiments. In the shadiest areas, I pulled up a small section of moss and seeded it with grass. In the rest of the yard, I let the moss go to see what would happen.

最阴凉地区的草种子从未茁壮成长。有些发芽了,但它永远无法很好地建立起来。应用部落神话会导致我院子的很大一部分都是裸露的泥土,坦率地说,我更喜欢苔藓而不是苔藓。它绿色葱郁,无需维护即可在该地区茁壮成长。对于该特定区域,苔藓是一个很好的地被解决方案。

The grass seed in the shadiest area never thrived. Some sprouted, but it could never get established well. Applying the tribal mythology would have resulted in bare dirt in a good section of my yard, and frankly, I prefer the moss to that. It is green and lush, and it thrives in that area without maintenance. For that specific area, moss is a good ground cover solution.

在院子里其他几乎没有或没有阴凉的地方,事实证明,苔藓和草或多或少可以很好地相处。草长得很好,苔藓不能直接克服它。不幸的是,苔藓有副作用。在阳光最充足的地区,苔藓充当保护层,防止杂草在下面发芽,防止鸟类和老鼠吃掉种子或嫩芽,并且适当湿润。苔藓不会呛死草,但杂草肯定会。通过让苔藓存在于阳光充足的地方,我为杂草提供了一个苗圃来扎根,当它们穿透苔藓时,它们在阳光下茁壮成长并迅速蔓延。苔藓还为杂草的根部提供了一个保护性的湿层,为它们提供了一个畅通无阻的生长通道。

In the rest of the yard with little or no shade, it turns out that moss and grass do play well together, more or less. The grass grows nicely, and the moss can’t overcome it directly. Unfortunately, the moss has a side effect. In the sunniest areas, the moss acts as a protective layer for weeds to sprout underneath, safe from birds and mice who would eat the seeds or shoots, and properly moist. The moss won’t choke out the grass, but the weeds definitely will. By letting the moss exist in the sunny areas, I was giving weeds a nursery to get established, and when they penetrated the moss, they thrived in the sunlight and spread rapidly. The moss also provided a protective moist layer for the roots of weeds to travel along, offering them an unhindered growth channel.

在最阴凉的地区,这不是问题,因为根本没有足够的阳光让草杂草生长良好,但在阳光充足的地区,情况就很可怕了。在几个月内,我就与杂草进行了一场名副其实的地盘争夺战。苔藓本身从来都不是问题,但它为更大的问题奠定了基础。

In the shadiest areas, this wasn’t a problem, because there simply isn’t enough sun for grass or weeds to grow well, but in the sunny areas, it was horrible. Within a couple of months, I was fighting a literal turf war with the weeds. The moss was never a problem by itself, but it set the scene for a much larger one.

现在我知道从草坪上去除苔藓的原因了。问题不在于苔藓,而在于苔藓创造了一个次生的小气候,从而造成了严重的情况。从本质上讲,苔藓创造了一组新的力量,形成了一个新的环境。在那个新的环境、新的环境中,出现了新的问题——就像杂草一样。现在去除苔藓的建议是有道理的,至少对于杂草成问题的区域,例如我院子里阳光充足的区域。

And now I know the why behind removing moss from a lawn. It’s not so much the moss that is the issue, it is that the moss creates a secondary microclimate that sets up a serious situation. Essentially, the moss creates a new set of forces at play that form a new context. Within that new context, that new environment, new problems arise—like weeds. Now the advice to remove the moss makes sense, at least for areas where weeds are an issue, such as the sunny areas of my yard.

因为我知道原因,所以我现在可以根据环境力量改变我对这些知识的应用。在阳光充足的地方,我必须清除并防止苔藓,这样杂草就不会成为以后的问题。在阴凉的地方,我选择不这样做,因为这样做会给我带来另一个问题,给我留下光秃秃的泥土,我很难让草长得好。

Because I know the why, I can now alter my application of this knowledge according to the environmental forces. In the sunny areas, I must remove and prevent the moss so that weeds are not a later problem. In the shady areas, I choose not to because doing so would create another problem for me, leaving me with bare dirt where I’d struggle to get grass to grow well.

事实上,因为我知道建议背后的原因,所以我可以根据环境(阳光明媚与阴凉)定制适合我得到的解决方案——“去除苔藓”——不仅可以解决我最初的问题,还可以防止产生新的问题。最初的部落神话现在是部落智慧,可以在适当的时候和地点分享、调整和应用。从本质上讲,它是一个模式的开始。

As it is, because I know the reasons behind the advice, I can custom fit the solution I was given—“Remove the moss”—based on the context—sunny vs. shady—and not only solve my initial problem but prevent new ones from being created. What was initially tribal mythology is now tribal wisdom that can be shared, adjusted, and applied when and where appropriate. In essence, it is the beginning of a pattern.

1.2. 艺术还是科学?

1.2. Art or Science?

毫无疑问,模式是一个蓬勃发展的模因,并且具有很大的实用性。现在,整个学术会议都致力于模式,Ackerman 和 Gonzalez 的基于模式的工程本身正在成为一门明确的学科 [2],行业顾问现在被期望拥有它们,并能够当场绘制出它们的统一建模语言 (UML) 图。存在用于生成、显示、生成和提取模式的工具。模式作为一个整体,是软件工程领域的一个假设组成部分。我们只是不太确定它们如何适应那种环境,或者它们如何相互适应。有两个问题阻碍了更全面的模式方法,不幸的是,它们在行业中无处不在。第一个是将模式视为盲目复制的冻结元素,第二个是将特定于语言的模式实现与模式本身的变体混淆。

There is no doubt that patterns are a thriving meme, and one with great utility. Entire academic conferences are now dedicated to patterns, Ackerman and Gonzalez’s patterns-based engineering is becoming a defined discipline in its own right [2], and industry consultants are now expected to have them under their belt and be able to whip out Unified Modeling Language (UML) diagrams of them on the spot. Tools exist to produce, display, generate, and extract patterns. Patterns, as a collective whole, are an assumed component of the software engineering landscape. We’re just not quite sure how they fit into that landscape or how they fit with each other. Two issues prevent a more comprehensive approach to patterns, and unfortunately they are ubiquitous in the industry. The first is treating patterns as frozen elements to be blindly copied, the second is confusing language-specific pattern implementations with variants of the patterns themselves.

1.2.1. 以死记硬背的方式查看模式

1.2.1. Viewing Patterns as Rote

让十几个开发人员定义设计模式,你可能会得到十几个答案。在更传统的 “a solution to a recurring problem within a particular context” 的答案中,你还可能会听到诸如 “a recipe” 或 “an example structure” 或 “some sample code” 之类的短语,这暴露了对模式所提供的相当狭隘的看法。模式旨在进行修改、扭曲和模塑,以满足在问题上下文中起作用的特定力量的需求,但很多时候,开发人员只是从模式文本或站点复制和粘贴示例代码,并宣布结果为模式的成功应用。这通常是失败的秘诀,而不是产生良好设计的秘诀。

Ask a dozen developers to define design patterns, and you’ll likely get a dozen answers. Among the more traditional “a solution to a recurring problem within a particular context” answers, you’re also likely to hear phrases such as “a recipe” or “an example structure” or “some sample code,” betraying a rather narrow view of what patterns provide. Patterns are intended to be mutated, to be warped and molded, to meet the needs of the particular forces at play in the context of the problem, but all too often, a developer simply copies and pastes the sample code from a patterns text or site and declares the result a successful application of the pattern. This is usually a recipe for failure instead of a recipe for producing a good design.

纯粹死记硬背模式的结构“因为这个权威是这么说的”是对亚历山大的无意识设计概念的回归。当我们这样做时,我们破坏了设计模式的整个目的。我们需要能够描述模式背后的原因以及如何描述。如果不了解导致该模式描述的原因,死记硬背通常会导致误用。充其量,结果是一个与预期结果不匹配的 broken pattern。在最坏的情况下,它会向系统注入一种医源性模式——一种预期并被认为有益的模式,但反而会产生可能需要数年时间才能发现的恶性结果。它不仅无法提供预期的增强,而且还主动创造了一个可能比原始问题更糟糕的新问题。这就是部落神话的模式——没有理解的行动。

Pure rote copying of the structure of the pattern “because this authority says so” is a reversion to Alexander’s concept of unselfconscious design. We undermine the entire purpose of design patterns when we do that. We need to be able to describe the whys behind a pattern as well as the hows. Without the understanding of the reasons that led to the description of that pattern, rote application often results in misapplication. At best, the result is a broken pattern that simply does not match the intended outcome. At worst, it injects an iatrogenic pattern into the system—one that is intended and thought to be of benefit but instead produces a malignant result that may take years to uncover. It doesn’t just fail to provide the expected enhancement, it actively creates a new problem that may be worse than the original one. This is patterns as tribal mythology—action without understanding.

传统的设计模式形式,如 设计模式 [21] 中所定义,解释了模式背后的原因 — 动机、适用性和后果 — 但由读者梳理出形成模式的基本概念。在某种程度上,这些子概念在每种模式的 Participants (What the pieces) 和 Collaborations (how do they rererelations) 部分中进行了描述,但同样,开发人员经常将这些子概念视为死记硬背的解决方案的清单,而不是构成解决方案的基础概念和抽象的描述。

The traditional design pattern form, as defined in Design Patterns [21], explains the whys behind a pattern—motivations, applicability, and consequences—but it is up to the reader to tease out the underlying concepts that form a pattern. To some degree, these subconcepts are described in the Participants (what are the pieces) and Collaborations (how do they relate) sections for each patterns, but again, these are frequently treated by developers as checklists of pieces of the solution for rote implementation instead of as a description of the underlying concepts and abstractions that comprise a solution.

1.2.2. 依赖于语言的视图

1.2.2. Language-Dependent Views

询问开发人员模式对他或她的工作有多重要,答案通常会基于开发人员使用的实现语言。这并不奇怪。不同的语言围绕它们支持的概念和表达方式提供不同的优势。这些概念如何恰好被表达出来,往往是语言爱好者之间激烈战争的开始,但在大多数情况下,忽视基本概念会导致很多争论,而不是任何特别重要的争论。块是像 C 系列那样用大括号分隔,还是像 Python 那样用空格分隔,这远不如一开始就有块的概念重要。

Ask a developer how important patterns are to his or her work, and frequently the answer will be based on the implementation language the developer is using. This isn’t surprising. Different languages offer different strengths centered around the concepts they support and how they express them. How those concepts happen to be expressed is more often the start of flame wars between language fans, but ignoring the underlying concepts leads to much argument over nothing of particular consequence in most cases. Whether blocks are delineated by curly braces, as in the C family, or by whitespace, as with Python, isn’t nearly as important as having the concept of blocks in the first place.

这意味着,某些模式在某些语言中比在其他语言中更容易实现。事实上,某些语言可以使某些模式背后的概念变得如此易于实现,以至于它们被称为语言功能。访客模式就是一个很好的示例。1 访客的实施部分 [21, 第 338 页] 说,“访客通过使用一种称为双重调度的技术来实现 [其目标]。这是一种众所周知的技术。事实上,一些编程语言直接支持它(例如 CLOS)。这是什么意思?这意味着向 CLOS(Common LISP Object System)开发人员提及 Visitor 设计模式会让他们摸不着头脑。“一种模式?对于语言功能?为什么?在 CLOS 中,Visitor 基本上是内置的。您不需要一个模式来告诉您如何最好地表达概念 — 它已经作为基本功能存在于语言中。然而,在大多数其他语言中,Visitor 提供了一种简洁的方式来表达相同的双重调度编程概念。

What this means is simply that some patterns are easier to implement in some languages than in others. In fact, some languages can make the concepts behind certain patterns so simple to implement that they’re known as language features. The Visitor pattern is a good example.1 Visitor’s Implementation section [21, pg. 338] says, “Visitor achieves [its goal] by using a technique called double-dispatch. It’s a well-known technique. In fact, some programming languages support it directly (CLOS, for example).” What does this mean? It means that mentioning the Visitor design pattern to CLOS (Common LISP Object System) developers will leave them scratching their heads. “A pattern? For a language feature? Why?” In CLOS, Visitor is essentially built in. You don’t need a pattern to tell you how to best express the concept—it’s already there in the language as a basic feature. In most other languages, however, Visitor provides a clean way of expressing the same programming concept of double dispatch.

这说明了一个重要的点。如果您向相同的 CLOS 开发人员提及 double dispatch 而不是 Visitor 模式,他们将知道您的意思、如何使用双重分派以及何时不使用它。术语,尤其是共享的通用术语,非常重要。

This illustrates an important point. If you mention double dispatch instead of the Visitor pattern to the same CLOS developers, they would know what you mean, how to use double dispatch, and when not to use it. Terminology, particularly shared common terminology, matters a great deal.

这适用于所有语言和所有模式:某些语言使某些模式更容易实现或微不足道,而其他模式则更难实现。然而,在这种情况下,没有一种语言可以真正被认为优于另一种语言。一个常见的误解是设计模式弥补了编程语言中的缺陷,但事实并非如此。设计模式描述有用的概念,而不管使用何种语言实现它们。特定概念是融入到语言的功能集中,还是必须从头开始实现,这无关紧要。这个概念仍然在实现中表达,这是让我们独立于软件实现来谈论软件设计的关键观察。设计就是概念;这些概念在给定语言中的具体化方式是实现。

This is true for all languages and all patterns: some languages make certain patterns easier or trivial to implement and other patterns more difficult to realize. No language can really be considered superior to another in this case, however. One common myth is that design patterns make up for flaws in programming languages, but that isn’t the case. Design patterns describe useful concepts, regardless of the language used to implement them. Whether a specific concept is baked into the feature set of a language or must be implemented from scratch is irrelevant. The concept is still being expressed in the implementation, and that is the critical observation that lets us talk about software design independently of software implementation. Design is concepts; how those concepts are made concrete in a given language is implementation.

当你深入研究时,你没有理由不能用纯 C 实现 GoF 文本中的每个模式,但这会非常乏味。您必须构建最佳实践,将数据和函数绑定到有意义的语义单元中,封装该数据,确保数据在首次可访问性时即可使用,等等。这听起来像是很多工作,但这些概念被认为非常重要,以至于它们在语言功能方面引发了一场革命,使它们更易于使用。这场革命是面向对象编程。

When you get down to it, there’s no reason you couldn’t implement every pattern in the GoF text in plain C—but it would be extremely tedious. You’d have to build up best practices for binding data and functions into meaningful semantic units, encapsulating that data, ensuring that data is ready to use at first accessibility, and so on. This sounds like a lot of work, but these were concepts considered so important that they launched a revolution in language features to make them easier to work with. That revolution was object-oriented programming.

在面向对象的语言中,这些概念作为称为类、可见性和构造函数的主要语言功能包含在内。同样,我们可以参考 GoF:“如果我们假设使用过程语言,我们可能会包含称为'继承'、'封装'和'多态性'的设计模式。作者认为这句话足够重要,以至于它出现在引言的第 1.1 节中。再一次,这是大多数开发人员似乎都忽略的一个基本点,所以让我重申一下。

In object-oriented languages, those concepts are included as primary language features called classes, visibility, and constructors. Again, we can refer to the GoF: “If we assumed procedural languages, we might have included design patterns called ‘Inheritance,’ ‘Encapsulation,’ and ‘Polymorphism.’” The authors felt that this statement was important enough that it appears in Section 1.1 in the Introduction. And yet again, this is a fundamental point that seems lost on most developers, so let me restate it.

模式是独立于语言的概念;只有当你在具有一组给定语言特性和结构的特定语言中实现它们时,它们才会形成并成为具体的解决方案。

Patterns are language-independent concepts; they take form and become concrete solutions only when you implement them within a particular language with a given set of language features and constructs.

这意味着谈论 “Java 设计模式”、“C++ 设计模式”、“WebSphere 设计模式” 等有点奇怪,即使我们都这样做。它是我们真正含义或应该含义的略带懒惰的简写形式:在 Java、C++、WebSphere 等中实现的设计模式,与语言或 API 无关。阿拉伯数字

This means that it is a bit strange to talk about “Java design patterns,” “C++ design patterns,” “WebSphere design patterns,” and so on, even though we all do it. It’s a mildly lazy form of shorthand for what we really mean, or should mean: design patterns as implemented in Java, C++, WebSphere, and so on, regardless of language or API.2

不幸的是,如果你和许多开发人员一样,读过一本关于设计模式的书籍,你可能已经接受过培训,或者至少被错误地引导相信,用 Java 表达的模式与用其他语言(如 Smalltalk)表达的模式之间存在一些短暂但根本的差异。真的没有。概念是相同的;只是它们的表达方式和程序员用该特定语言实现它们的难易程度不同。

Unfortunately, if you’re like many developers who have encountered one of the multitude of books on design patterns, you may have been trained, or at least have been erroneously led to believe, that there is some ephemeral yet fundamental difference between patterns as expressed in Java and those expressed in another language such as Smalltalk. There really isn’t. The concepts are the same; only the manner in which they are expressed and the ease with which a programmer can implement them in that specific language differ.

在研究设计模式时,我们需要关注这些,而这些抽象必须是理解模式的关键。除非我们努力将模式视为独立于语言的概念,否则我们只是在再次学习死记硬背的配方,并失去了它们如此有用的大部分东西。

We need to focus on these when investigating design patterns, and these abstractions must be the crux of understanding patterns. Unless we make the effort to look at patterns as language-independent concepts, we are merely learning rote recipes again and losing much of what makes them so useful.

1.2.3. 从神话到科学

1.2.3. From Myth to Science

前面描述的问题掩盖了设计模式的一个潜在问题,因为它们今天经常被传达、使用和理解。很多时候,我们仍然不知道为什么要做我们所做的,即使我们在代码中使用了设计模式。通过如此灵活地使用设计模式,我们只是更好地记录了一组无意识的片段,而没有从对片段进行系统分析中获得的理解。

The issues described previously belie an underlying problem with design patterns as they are often conveyed, used, and understood today. All too often, we still don’t know why we do what we do, even when we use design patterns in our code. By using design patterns so inflexibly, we’ve simply better documented a body of unselfconscious snippets without the comprehension that comes from a methodical analysis of the snippets.

我们有一门艺术。我们需要的是一门科学。毕竟,我们随意抛弃了计算机科学和软件工程这两个术语。将模式视为示例代码错过了设计模式的重点。设计模式使我们能够作为一个行业来试验这些概念并分享、讨论和完善我们的发现。

We have an art. What we need is a science. After all, we throw around the terms computer science and software engineering with abandon. Treating patterns as sample code misses the point of design patterns. Design patterns enable us as an industry to experiment with those concepts and share, discuss, and refine our findings.

作为死记硬背食谱的模式是部落神话。

Patterns as rote recipes are tribal mythology.

作为概念的模式是一门科学的基础。

Patterns as concepts are the foundations of a science.

元素设计模式是这门科学的基石。

Elemental Design Patterns are the building blocks of that science.

2. 元素设计模式

2. Elemental Design Patterns

基本设计模式 (EDP) 的核心是面向对象编程的基本概念的目录。两个特征使它们独一无二:首先,它们是以设计模式文献的风格编写的。每个概念都被视为一个独立的概念,有一个特定的名称,通过这个概念可以进行讨论和考虑,直到理解它。本书介绍了每个 EDP 及其适用的问题,并讨论了何时应该应用和不应该应用该模式。您将找到示例实现、对使用这些模式的可能后果的注释,以及您应该查看的相关模式。每个 EDP 的这种以人为本的定义为您提供了一个通用术语,您可以使用该术语与其他学生或开发人员清晰准确地讨论该概念。

The Elemental Design Patterns, or EDPs, are, at their core, a catalog of the basic concepts of object-oriented programming. Two traits make them unique: First, they are written in the style of the design patterns literature. Each is treated as a standalone concept with a specific name by which it can be discussed and mulled over until it is understood. This book presents each EDP along with a problem that it is suited for and discusses when the pattern should and should not be applied. You will find example implementations, comments on possible consequences of using the patterns, and related patterns that you should look at as well. This human-oriented definition of each EDP provides you with a common term you can use to clearly and precisely converse with other students or developers about the concept.

其次,设计模式是通过检查现有软件系统发现的常见问题的解决方案的描述。EDP 也是常见问题的解决方案,一旦您知道要寻找什么,您就会无处不在。事实上,它们非常普遍,以至于直到现在,人们还不认为它们值得以任何全面的方式来写。EDP 最初被确定为相互关联的设计概念的综合体,而不是在软件中,而是在软件的正式描述中。EDP 源于面向对象语言的数学基础,但请放心,除了安全地塞在后面的附录外,此文本是无数学的。(不过,我确实希望您能好奇并阅读附录 — 它相当有趣。EDP 背后的坚实而简单的思想提供了一个强大的框架,可以在此基础上重新审视这些基本概念,赋予它们新的生命,并将它们重新用于新的应用程序。这个基础还为您提供了一种令人信服的能力,可以将它们用作微小的构建块,几乎就像原子一样,以坚实和直接的方式描述其他设计模式。而且,就像原子一样,你可以用它们构建世界。

Second, design patterns are descriptions of solutions to common problems that were found through the examination of existing software systems. EDPs are also solutions to common problems, and once you know what to look for, you will find them everywhere. They are in fact so common that, until now, they have not been considered worth writing up in any comprehensive way. EDPs were originally identified as a comprehensive body of interrelated design concepts not in software but in a formal description of software. EDPs arise from the mathematical foundations of object-oriented languages, but rest assured, with the exception of an appendix safely tucked in the back, this text is mathematics free. (I do hope you get curious and read the appendix, though—it’s rather interesting.) The solid yet simple ideas underlying the EDPs provide a strong framework on which to revisit these basic concepts, give them new life, and repurpose them for new applications. This foundation also gives you a compelling ability to use them as tiny building blocks, almost like atoms, to describe other design patterns in solid and direct ways. And, like atoms, you can build worlds with them.

这本书为你提供了一些关于 EDP 的背景知识,它们最初的来源,以及它们如何适应编程中设计模式的更大背景。它还让你对面向对象理论有足够的了解,以理解模式是如何相互关联的,但我保证,没有方程式。我们将讨论面向对象编程基础知识的一些基本概念,其中大部分您可能已经很熟悉。最后,您将了解该基本理论如何产生可在日常编程中使用的名副其实的稳定概念。后面的章节解释了如何使用 EDP 来改进您的设计。

This book gives you some background on EDPs, where they came from originally, and how they fit into the larger context of design patterns in programming. It also gives you just enough of a taste of object-oriented theory to understand how the patterns all relate to one another, but no equations, I promise. We discuss some of the ideas underlying the basics of object-oriented programming, much of which you may already be familiar with. Finally, you learn how that basic theory gives rise to a veritable stable of concepts that you can use in your everyday programming. Later chapters explain how you can use EDPs to make your designs better.

2.1. 背景

2.1. Background

EDP 的识别始于北卡罗来纳大学教堂山分校的模式查询和识别系统 (SPQR) 项目 [35]。SPQR 是一个研究项目,用于识别现有源代码正文中已知设计模式的实例。它可以找到独立于原始源代码语言的设计模式,并且可以从单个模式定义中轻松找到同一设计模式的各种实现。如果您对 SPQR 的内部工作原理感兴趣,可以获得更多信息 [353738],但基本理解为我们讨论什么是 EDP 以及如何定义它们提供了足够的背景。

The identification of EDPs had its beginnings in the System for Pattern Query and Recognition (SPQR) project at the University of North Carolina at Chapel Hill [35]. SPQR is a research project for identifying instances of known design patterns within an existing body of source code. It can find design patterns independent of the original source code language and can easily find various implementations of the same design pattern from a single pattern definition. If you’re interested in the inner workings of SPQR, further information is available [35, 37, 38], but a basic understanding provides enough context for our discussion of what EDPs are and how they are defined.

我在担任专业软件工程师时受到启发创建了 SPQR。我所在的团队负责实时商业和军事飞行仿真系统中使用的三个库之一。在我们与应用程序团队的一次联合全库开发会议上,他们对我们刚刚添加的功能表示了衷心的感谢,并表示这正是他们所需要的。我们说他们很受欢迎,这就是我们在那里的目的,等等。

I was inspired to create SPQR while working as a professional software engineer. I was on a team responsible for one of three libraries used in a real-time commercial and military flight simulation system. In one of our joint all-library development meetings with the application team, they thanked us profusely for a feature we had just added, stating that it was exactly what they needed. We said they were quite welcome, that’s what we were there for, and so on.

会议结束后,三个库团队面面相觑,询问其他人是否知道应用团队一直在谈论什么。我们都没有设计他们所描述的功能,我们甚至不确定它是如何产生的,或者它是否可能。我们决定追根究底,认为这肯定不会花费太多时间。

After the meeting, the three library teams looked at each other and asked if the others knew what the application team had been talking about. None of us had designed the feature they were describing, we weren’t even sure how it had come about, or that it was possible. We decided to get to the bottom of it, thinking that certainly it couldn’t take much time.

将近 200 个小时的开发人员后,我们有了答案。我们有一个 Decorator 模式的实例,它是 Gang of Four (GoF) 模式之一,嵌入和隐藏在系统中。独特的是,它不存在于任何一个库中。其中的部分分散在三个库中,应用程序团队偶然发现了它们作为一个整体的集成。我们惊呆了。一场相当激烈的争论接踵而至:这真的是一种设计模式吗?毕竟,它不是设计出来的,它只是在某种程度上成长了。

Nearly 200 developer hours later, we had our answer. We had an instance of a Decorator pattern, one of the Gang of Four (GoF) patterns, embedded and hidden within the system. What was unique was that it didn’t live in any one library. Pieces of it were scattered across the three libraries, and the application team had stumbled onto their integration as a whole. We were stunned. A rather heated debate ensued: Was this really a design pattern? After all, it hadn’t been designed, it had just sort of grown.

嗯,大多数软件就是这样做的。它是有机生长的,而且往往以意想不到的方式生长。在管理软件项目时,我们只能管理我们所知道的,并且知道存在这种有用的设计元素为我们提供了一个增强、简化和记录其存在以供开发人员有效使用的机会。找到它——这就是问题所在。请记住,我们已经编写了有问题的软件,我们是专家,我们仍然花费了大量的时间来推断发生了什么。流程自动化是显而易见的答案。正如我在第 1 章中提到的,我们真正需要的是自文档化的代码,或者至少是一个提取该文档的系统。SPQR 就是这样一个系统。

Well, most software does just that. It grows organically, and often in unexpected ways. In managing software projects, we can only manage what we know, and knowing that there was this useful design element provided us an opportunity to enhance, streamline, and document its existence for developers to use effectively. Finding it—that was the problem. Remember, we had written the software in question, we were the experts, and it still took us an inordinate amount of time to deduce what was going on. Automation of the process was the obvious answer. As I mentioned in Chapter 1, what we really want is self-documenting code, or at least a system to extract that documentation. SPQR is such a system.

创建 SPQR 需要解决一个基本问题:我必须教计算机如何识别设计模式。模式不是死记硬背的食谱;它们是我们人类称之为概念的柔软而无定形的东西。大多数试图在源代码中查找模式实例的研究和行业系统都是通过将模式视为结构 — 看起来像实现的刚性形式 — 而不是抽象概念来看待的。这是一种合理的方法,它像许多开发人员一样处理模式:“这个类有那个字段,并且该子类通过一个名为 X 的方法访问它”,依此类推。问题是,这种方法再次将设计模式简化为特定的实现。可能实现的每个变体都需要一个新的定义。这又是剪切和粘贴的方法 [2530]。我并不是说这项工作质量低下——远非如此。这些工具是该方法的出色示例,值得您研究。他们做得很好,如果您碰巧满足他们的要求,他们会增加价值。

Creating SPQR necessitated solving a fundamental problem: I had to teach the computer how to identify design patterns. Patterns are not rote recipes; they are soft and amorphous things we humans call concepts. Most research and industry systems that attempt to find instances of patterns in source code do so by looking at patterns as constructs—as rigid forms that look like implementations—rather than as abstract concepts. This is a reasonable approach, and it treats patterns as many developers do: “This class has that field, and that subclass accesses it through a method named X,” and so on. The thing is, this approach once more reduces the design patterns to specific implementations. Every variation of a possible implementation requires a new definition. It’s the cut-and-paste methodology again [25, 30]. I don’t mean that this work is of low quality—far from it. These tools are stellar examples of the approach and are worth your investigation. They do their job well, and they add value if you happen to meet their requirements.

这种方法的问题在于,将查询模式定义为构造是非常脆弱的。如果您定义的特定方法不是命名为 X,而是命名为 Y,该怎么办?如果子类不是直接子类,但在继承层次结构中两者之间有另一个类怎么办?或者,如果子类根本不是真正的继承子类,而是使用超类作为委托对象怎么办?这些条件都不能通过面向构造的设计模式方法找到,它们需要新的模式定义。这是假设你仍然在使用一种实现语言。请记住,模式与实现语言无关 — 问题的范围要大得多。

The problem with this approach is that defining a pattern for queries as a construct is highly fragile. What if the particular method you define wasn’t named X, but Y? What if the subclass wasn’t a direct subclass, but there was another class between the two in the inheritance hierarchy? Or what if the subclass wasn’t a true inherited subclass at all but used the superclass as a delegate object instead? None of these conditions would be found by a construct-oriented approach to design patterns, and they would require new pattern definitions. And that’s assuming you’re still working within one implementation language. Remember, patterns are implementation-language independent—the scope of the issue is much larger.

很明显,SPQR 需要一种不同的策略。深入研究设计模式的背景和历史产生了必要的线索。我对 GoF 材料的研究越多,并回溯产生它的灵感,我就越专注于 Alexander 的原创作品。当我阅读他的第一篇,至少在软件工程社区中,引用最少的关于设计模式的论文 Notes on the Synthesis of Form [4] 时,我意识到丢失了一些重要的东西:模式是概念,而不是结构。

It seemed obvious that a different tactic was needed with SPQR. Diving into the background and history of design patterns yielded the necessary clue. The more I studied the GoF material and worked back through the inspirations that spawned it, the more I focussed on Alexander’s original work. As I read his first and, at least within the software engineering community, least-cited treatise on design patterns, Notes on the Synthesis of Form [4], I realized something important had been lost: patterns are concepts, not constructs.

这个简单的事实从根本上改变了我试图教计算机识别的东西。教计算机系统寻找用刚性结构固定在一起的语言结构,永远不会给我们带来我们想要的灵活性和我们需要的准确性。相反,我们应该教系统寻找编程概念,因为这就是模式的真正含义。

This simple truth fundamentally alters what I was trying to teach the computer to recognize. Teaching a computer system to look for language constructs bolted together in rigid structures will never give us the flexibility we desire with the accuracy we need. Instead, we should be teaching a system to look for programming concepts because that’s what patterns really are.

不幸的是,对于这项特定任务,已建立的设计模式文献主要面向描述高级抽象概念,这是它们如此强大的部分原因 — 它们将设计的讨论提升到更高的抽象级别。程序员通常对较低级别的抽象有很好的工作处理能力,因此将它们建立为模式似乎从来都不是必要的。设计模式社区理所当然地专注于记录那些尚未根深蒂固和未被充分理解的经验教训。软件社区描述的这些设计模式往往处于范围的较大一端。另一方面,计算机的知识范围仅限于我们可以教给它们的基础。对于 SPQR,我们必须从头开始建立概念链,以便它可以帮助我们处理编程的概念。我们必须填补实现中高度僵化的部分与它们所代表的有时流动的概念之间的空白。

Unfortunately for this particular task, the established design patterns literature is primarily geared toward describing high-level, abstract concepts, which is part of what makes them so powerful—they lift the discussion of a design to a higher level of abstraction. Programmers generally have a good working handle on lower-level abstractions, so establishing them as patterns has never seemed necessary. The design patterns community is rightly focused on documenting those lessons that are not already ingrained and well understood. These design patterns described by the software community tend to be at the larger end of the spectrum. Computers, on the other hand, are fundamentally only as knowledgable as the foundation we can teach them. For SPQR, we had to establish a chain of concepts from the ground up so it could help us deal with the concepts of programming. We had to fill in the gap between the highly rigid pieces of an implementation and the sometimes fluid concepts that they represent.

在此过程中,很明显 EDP 就是这样 — 设计模式构成了描述软件设计的基本基础,不仅用于自动化分析,也适用于人类。这本书是为人类而非机器消费而编写的 EDP 的部分目录。它旨在供开发人员、学生和设计人员使用,以帮助填补我们在讨论软件设计时的理解和语言方面的空白。正如设计模式所预期的那样,每个 EDP 都有一个非正式的文章或模式规范,以使用一个公认的术语,并且还有一个数学结构的来源。后一个特征使 EDP 目录独一无二。每个 EDP 都是编程的抽象,它允许我们讨论软件设计中非常小的问题,而与它们的实现方式无关。

In doing so, it became apparent that the EDPs were just that—design patterns that form a fundamental basis from which to describe software design not just for automated analysis but for humans as well. This book is a partial catalog of the EDPs written for human, not machine, consumption. It is intended to be used by developers, students, and designers to help fill in the gaps in our understanding and our language when discussing software design. As expected with design patterns, each EDP has an informal write-up, or pattern specification, to use an accepted term, and also has an origin as a mathematical construct. This latter trait makes the EDP catalog unique. Each EDP is an abstraction of programming that lets us talk about even very small issues of software design independent of precisely how they are implemented.

这给我们带来了描述 EDP 的两种方式:它们的理论基础和实用主义的起源。以下部分将两者交织在一起,同时将形式主义保持在最低限度。你不需要在数学层面上精确地理解正式的基础就可以继续阅读本书,但如果你想朝着这个方向前进,你可能会发现它很有启发性。如果你发现自己对底层机制感到好奇,并希望更深入地了解设计模式和编程语言理论是如何相交的,你可以从附录开始,它描述了构成这项工作基础的 ρ 演算。

This brings us to two ways of describing EDPs: their basis in theory and their genesis in pragmatism. The following section intertwines the two while keeping the formalisms to a minimum. You don’t need to precisely understand the formal foundation at a mathematical level to continue in this book, but you may find it illuminating if you wish to move in that direction. If you find yourself wondering about the underlying mechanics and want a stronger understanding of how design patterns and programming language theory intersect, you can start in the appendix, which describes the ρ-calculus that forms the foundation of this body of work.

2.2. 地点、原因、方式

2.2. The Where, the Why, the How

教计算机寻找大规模的抽象和称为设计模式的概念,因为它们存在于常见文献中,是一项艰巨的任务。当面临计算机科学中一项庞大而艰巨的任务时,我们该怎么办?我们将其分解。

Teaching a computer to look for the large-scale abstractions and concepts called design patterns, as they exist in the common literature, is a difficult task. What do we do when faced with a large and gnarly task in computer science? We break it down.

解构设计模式文献并不是一件简单的事情。多年来已经进行了一些尝试 [121732404143],但它们都是部分解构,被直接领域以外的大多数开发人员和研究人员视为奇怪或好奇的东西。毕竟,这些较小的解构作品和概念是显而易见的,对吧?它们是基本概念,我们每天都要处理的基本事情,那么为什么还要费心去描述我们已经觉得自己知道的东西呢?

Deconstructing the design patterns literature is not a simple thing. A few attempts have been made over the years [12, 17, 32, 40, 41, 43], but they have been partial deconstructions, seen as oddities or curiosities by most developers and researchers outside the immediate field. After all, these smaller deconstructed pieces and concepts are obvious, right? They are basic concepts, basic things we deal with every day, so why bother describing what we already feel we know?

那么,如果它们如此明显,为什么它们还没有被记录下来呢?

Well, if they’re so obvious, how come they’re not already documented?

软件设计的基本概念与建立住所的“正确”方式一样“显而易见”:这完全取决于您的环境、经验和所学的内容。接受过 ML 函数式编程培训的人与主要背景是 C 的人对递归的理解非常不同。这两个开发人员对递归、递归的有用之处以及应该如何应用递归有不同的假设,即使它解决的基本基本问题是相同的。鉴于第 1 章中对住房风格的讨论,这个想法应该听起来很熟悉。

The basic concepts of software design are as “obvious” as the “correct” way to build a domicile: it depends entirely on your context, your experience, and what you were taught. Someone who was trained in functional programming in ML understands recursion very differently from someone whose primary background is C. The two developers will have different assumptions about recursion, where it is useful, and how it should be applied, even though the basic underlying problem that it is solving is the same. Given the discussion of housing styles in Chapter 1, this idea should start to sound familiar.

我在本章前面说过,编程的低级概念并不是设计模式社区的主要目标,因为这些文献旨在记录既不根深蒂固也不容易理解的概念。简单的概念被视为根深蒂固的,因此超出了该范围,但这并不意味着它们实际上被很好地理解。

I said earlier in this chapter that the lower-level concepts of programming have not been a primary target of the design patterns community because the literature is aimed at documenting concepts that are neither ingrained nor well understood. Simple concepts are seen as ingrained and therefore outside of that scope, but that doesn’t mean they’re actually well understood.

正如我们 Alexander 所定义的那样,我们在编程中使用的低级概念是无意识的。我们或多或少地通过在课堂上和现场死记硬背来学习它们,我们应用它们是因为“这就是它的运作方式”,而不必做出有意识的决定。我们大多数人从不研究这些概念背后的原则,但它们存在并且在数学层面上得到了很好的理解。

The low-level concepts we use in programming are unselfconscious, as Alexander defined it. We learn them more or less by rote in class and in the field, and we apply them because “that’s the way it works,” without having to make conscious decisions to do so. Most of us never study the principles underlying those concepts, but they exist and are well understood at a mathematical level.

这些概念只是没有在人类层面上得到适当的揭示,这就是 EDP 的意义所在。简而言之,EDP 是编程和软件设计的基本核心概念,大部分时间都没有被描述。直到现在,那些已经被描述的还没有以有意义和有根据的方式相互关联。每个 EDP 都是对一个核心概念的自我意识描述。EDP 目录作为一个整体将 EDP 彼此关联起来,以形成一个概念框架,学生或开发人员可以使用它来理解其他模式。它提供了一个分类和词典,用于描述更高级别的抽象,使语言和抽象同质化,以便当任何两个开发人员引用 EDP Extend Method 等时,他们都对通用定义有精确的理解。

Those concepts just haven’t been properly exposed at a human level, and that’s what EDPs are. In a nutshell, EDPs are the underlying core concepts of programming and software design that have remained mostly undescribed. Those that have been described have not been related to one another in a meaningful and well-founded manner until now. Each EDP is a selfconscious description of a core concept. The EDP catalog as a whole relates the EDPs to one another to form a conceptual framework that the student or developer can use to understand other patterns. It provides a taxonomy and lexicon for describing higher-level abstractions, homogenizing the language and abstractions such that when any two developers reference, for example, the EDP Extend Method, they both have a precise understanding of a common definition.

EDP 提供了一种语言,用于从最基本的层次开始推理、描述和讨论软件。主流设计模式在为经验丰富的专业人士提供此知识库方面做得非常出色,但直到现在,还没有人以全面的方式将相同的工具针对新手或学生。

EDPs provide a language with which to reason about, describe, and discuss software, from the most fundamental levels on up. Mainstream design patterns have done an amazing job of providing this knowledge base for the seasoned pro, but until now, no one has aimed that same tool at the novice or student in a comprehensive way.

那么,我们可以从设计模式文献中解构这些概念到什么程度呢?作为一个例子,让我们看看 Decorator

So how far can we deconstruct these concepts from the design patterns literature? As an example, let’s look at Decorator.

2.2.1. 装饰的分解

2.2.1. Decomposition of Decorator

在我们进一步讨论之前,关于命名约定的快速说明:本文中的 pattern 名称是斜体和大写的——例如,Extend Method。示例中的类型和类大写,并以 monotype 字体设置。字段和方法以大写字母或“驼峰式”和 monotype 字体设置,如 .代码示例使用相同的系统。thisIsAMethod()

A quick note on naming conventions before we go any further: pattern names in this text are italicized and capitalized—for example, Extend Method. Types and classes in examples are capitalized and set in monotype font. Fields and methods are set in intercaps, or “camelcase” and in monotype font, as in thisIsAMethod(). Code examples use the same system.

GoF 的设计模式 [21] 中提供的 Decorator 的规范示例统一建模语言 (UML) 如图 2.1 所示。现在,您不需要了解 Decorator 是什么;对 UML 图进行目视检查就足够了。通常,Decorator 是一种流行且常用的设计模式,它提供了一种在运行时动态扩展行为的机制。您可以将其视为内部插件或扩展系统,就像在 Web 浏览器中找到的那样。

The canonical example Unified Modeling Language (UML) for Decorator, as provided in the GoF’s Design Patterns [21], is shown in Figure 2.1. For now, you don’t need to understand what Decorator is; a visual inspection of the UML diagrams should suffice. In general, Decorator is a popular and commonly used design pattern that provides a mechanism for behavior to be extended dynamically at runtime. You can think of it as an internal plug-in or extension system, like you would find in a web browser.

图像

图 2.1.Decorator 的常用示例 UML。

Figure 2.1. Decorator’s usual example UML.

假设你想分解 Decorator 以更好地理解它。毕竟,它是一个相当高级的抽象。如果你能单独吸收较小的碎片,你几乎可以肯定更容易理解模式。更好的是,您应该能够使用这些较小的部分来帮助您理解您可能遇到的其他设计模式,只要它们以类似的术语进行描述即可。

Suppose you want to decompose Decorator to better understand it. It is, after all, a rather high-level abstraction. If you can absorb smaller pieces individually, you can almost certainly have an easier time understanding the pattern. Better still, you should be able to use those smaller pieces to help you understand other design patterns you may encounter, as long as they’re described in similar terms.

这与试图了解赛车等复杂机械的工作原理没有什么不同。你可以买一个,把它放在你的车库里,拆开工具,然后拆解整辆车,或者你可以单独了解这些部件。例如,您可以研究内燃机或液压制动系统。这些系统可以进一步分解为多个部分,您可以一一研究这些部分,以使更大的研究任务更容易。

It’s not unlike trying to understand how a complex piece of machinery such as a race car works. You can buy one, put it in your garage, break out the tools, and disassemble the whole car, or you can learn about the pieces individually. You can study the internal combustion engine, for instance, or the hydraulic braking system. Those systems can be further broken down into parts that you can investigate one by one to make the larger study task easier.

最后,如果你知道汽油发动机的工作原理,你不仅可以将赛车的这一部分理解为一个单元,作为一个封装的抽象概念,而且你可以将这些知识应用于其他车辆,甚至割草机、发电机和任何其他汽油发动机驱动的机器。分解设计模式也有类似的目的:确定你可以根据自己的优点处理的小块,在更容易理解的块中深入学习,并应用于新情况。

In the end, if you know how a gasoline engine works, you not only have the facility to comprehend that portion of the race car as a unit, as an encapsulated abstraction, but you can apply that knowledge to other vehicles, or even lawnmowers, generators, and any other gasoline-engine-driven machine. Decomposing design patterns serves a similar purpose: identifying the smaller pieces that you can treat on their own merits, learn thoroughly in more digestible chunks, and apply in novel situations.

在详尽搜索有关设计模式的现有文献(请记住,有数千种)之后,您可能会注意到 Decorator 与其他两种模式有相似之处。

After an exhaustive search of the existing literature on design patterns (and remember, there are thousands), you might notice that Decorator shares similarities with two other patterns.

一种是客观化器,由 Walter Zimmer 于 1995 年首次描述 [43],如图 2.2 所示。Objectifier 描述了一种让单个对象接口以对客户端不透明的方式表示多个具体实现的方法。当客户端请求通过 Objectifier 接口调用方法时,它不知道执行两个(或多个)具体方法体中的哪一个。

One is Objectifier, first described by Walter Zimmer in 1995 [43], shown in Figure 2.2. Objectifier describes a way for a single object interface to represent multiple concrete implementations in a way that is opaque to a client. When a client requests a method to be invoked through the Objectifier interface, it does not know which of the two (or more) concrete method bodies is executed.

图像

图 2.2.Objectifier 作为 UML。

Figure 2.2. Objectifier as UML.

另一个是对象递归,由 Bobby Woolf 在 1998 年[41]概述,如图 2.3 所示。Object Recursion 将两个具有相关类型的对象链接在一起。

The other is Object Recursion, outlined by Bobby Woolf in 1998 [41], shown in Figure 2.3. Object Recursion chains together two objects with related types.

图像

图 2.3.对象递归作为 UML。

Figure 2.3. Object Recursion as UML.

查看图 2.2 和图 2.3 中的 UML 图,您可以看到 Objectifier 和 Object Recursion 看起来与 Decorator 模式的片段非常相似。尽管它们并不完全相同,但它们的 UML 结构中的某些特征非常相似,以至于我们可以用这些其他模式来描述 Decorator 的各个部分——我们可以说 Decorator 是由这些模式组成的。

Looking at the UML diagrams in Figures 2.2 and 2.3, you can see that Objectifier and Object Recursion look quite a bit like pieces of the Decorator pattern. Although they are not exactly the same, certain features in their structure of the UML are similar enough that we can describe parts of Decorator in terms of these other patterns—we can say that Decorator is composed of these patterns.

仔细观察会发现,Objectifier 可以被视为 Object Recursion 的一部分。Object Recursion 使用 Objectifier 作为其形式的主干,但它在其中一个具体实现和接口之间添加了一个链接,并且它对相同的方法也是如此。换句话说,当类根据 Objectifier 的意图调用 , 或 类可能会处理它。如果处理它,则请求完成。If 处理它,则通过接口对另一个 进行其他调用,然后该过程将重新开始。此链一直持续到处理程序为止,此时链不出所料地终止。InitiatorhandleRequest()TerminatorRecursorTerminatorRecursorhandleRequest()Terminator

A closer look shows that Objectifier can be considered a part of Object Recursion. Object Recursion uses Objectifier as the backbone of its form, but it adds a link between one of the concrete implementations and the interface, and it does so for the same method. In other words, when the Initiator class calls handleRequest(), by the intent of Objectifier, either the Terminator or Recursor class might handle it. If Terminator handles it, the request is complete. If Recursor handles it, an additional call is made through the interface for another handleRequest(), and the process starts over again. This chain continues until Terminator is the handler, at which point the chain, unsurprisingly, terminates.

根据这一系列操作,我们可以说,“装饰器使用对象递归来遍历对象链,大概长度至少为两个,并对每个对象执行相同的方法调用。不过,这似乎相当模糊,并且它也错过了 Decorator 中的很多细节。我们应该有一个更好的分解来使用,但至少我们知道模式可以用更小的模式来描述。

Given this series of actions, we can say, “A Decorator uses Object Recursion to traverse a chain of objects, presumably at least two in length, and perform the same method call on each.” That seems rather vague, though, and it also misses a lot of the detail in Decorator. We should have a better decomposition to work with, but at least we know that patterns can be described in terms of smaller patterns.

2.2.2. 进入兔子洞

2.2.2. Down the Rabbit Hole

我们已经确定我们可以从一些较小的部分构建 Decorator,但这些部分是否尽可能小?我们能做出的最小解构模式是什么,但仍然是一个模式?

We’ve established that we can build Decorator from some smaller pieces, but are these pieces as small as they can be? What is the smallest deconstructed pattern we can make that is still a pattern?

要回答这个问题,我们需要再次问,什么是模式?我们知道它是一个概念,我们知道它是设计的一个元素,我们知道它有某些关键组成部分。让我们先把概念是什么的问题放在一边,解决其他两个部分。设计是整体各部分相互交互和相互关联的方式,如以 GoF 文本中普遍描述的公认的规范格式编写的设计模式大纲所示。GoF 的大纲表明,参与者和协作是设计的核心部分,它们突出了设计的各个部分以及它们如何相互交互和关联。

To answer that question, we need to ask again, What is a pattern? We know it is a concept, we know it is an element of design, and we know has certain critical components. Let’s set aside the question of what a concept is and address the other two portions. Design is the manner in which parts of a whole interact with and relate to one another, as illustrated in the outline of a design pattern written in the accepted canonical format popularly described in the GoF text. GoF’s outline shows that participants and collaborations are core pieces of a design, and that they highlight both the parts of the design and how they interact with and relate to each other.

考虑设计模式的常见定义:“特定上下文中常见问题的通用解决方案”。规范格式设计模式规范的大部分内容可以很容易地归为这三类:解决方案(或实现)、问题(或描述)和上下文(或环境),如表 2.1 所示。结构、实现和示例代码无疑是解决方案。意图、动机和已知用途描述了问题空间。上下文(出现问题的环境)指导适用性,构建对可能后果的讨论,并通常表明当前模式是否不完全正确,以及哪些相关模式是更好的选择。

Consider a common definition of design pattern: “A common solution to a common problem within a particular context.” Most of the pieces of a canonical format design pattern specification can easily be placed into those three categories: solution (or implementation), problem (or description), and context (or environment), as in Table 2.1. The structure, implementation, and sample code are unmistakably the solution. The intent, motivation, and known uses describe the problem space. The context, the environment in which the problem occurs, guides applicability, frames the discussion of possible consequences, and often indicates if the current pattern isn’t exactly right and which of the related patterns is a better choice.

表 2.1.分为样式定义的三个类别的样式块

Table 2.1. Pattern pieces sorted into three categories of a pattern definition

图像

对这些元素进行排序后,我们只剩下两个部分,它们不容易归入这三个类别之一:参与者和协作。它们显然是解决方案的一部分,因为它们共同构成了解决方案的大部分。但它们也是问题描述的一部分,因为它们几乎总是直接反映问题的表述方式。最后,它们是上下文的一部分,因为它们是根据它们所在环境的需求而创建的。它们描述了位于设计模式的三个分支交汇处的部件及其关系。

After sorting these elements we have two parts left that don’t easily fit into one of those three categories: participants and collaborations. They’re obviously part solution in that together they form much of the solution. But they’re also part problem description because they almost always directly reflect how the problem is phrased. Finally, they’re part context in that they are created in response to the needs of the environment in which they are placed. They describe the parts and their relationships that lie at the intersection of the three arms of a design pattern.

关系构成了设计的核心。汽车的设计不仅仅是一堆碎片:它也是它们如何组合在一起的蓝图。房子不仅仅是一堆木材、一箱钉子和一些铜管;它有一种形式或计划,可以对抗熵,我们可以说,将结构保持在比瓦砾堆更高的能量水平。零件清单确保您拥有开始构建所需的一切,但设计中的关系告诉您如何使它们协同工作。

Relationships form the core of design. The design of a car is more than a pile of pieces: it is also a blueprint for how they fit together. A house is more than a stack of lumber, a case of nails, and some copper piping; it has a form, or plan, that fights entropy and, we could say, keeps the structure at a higher energy level than just a rubble pile. The parts list ensures that you have everything you need to begin building, but the relationships in the design tell you how to make them work in concert.

让我们稍微翻转一下问题:我们可以定义的最小关系是什么?嗯,这很容易。两件事之间的单一关系是最简单的。现在我们可以将这种洞察力应用于设计模式的解构。

Let’s flip the question around a bit: What is the smallest relationship we can define? Well, that one is easy. A single relationship between two things is simplest. Now we can apply this insight to the deconstruction of design patterns.

这给了我们一个明确的目标。对于每个后续较小的设计模式,我们可以用批判的眼光看待它,并问道:“这是否体现了不止一种关系?如果没有,那么我们肯定已经达到了我们的目标。如果是这样,我们仍然有可能已经这样做了。

This gives us a clear goal. With each subsequent smaller design pattern, we can look at it with a critical eye and ask, “Does this embody more than one relationship?” If not, then we’ve definitely reached our goal. If so, it’s still possible that we’ve done so.

如果涉及不止一种关系,为什么我们已经“可能”完成了呢?嗯,并非所有关系都是平等的。有些在讨论设计时非常重要,有些则用于提供背景。主要的上下文关系是范围界定的关系,它以多种形式出现。

Why would we already “possibly” be done if there is more than one relationship involved? Well, not all relationships are created equal. Some are quite important when discussing design, and some are there to provide a context. The primary contextual relationship is that of scoping, and it appears in many forms.

无论何时声明变量、定义方法或描述类,无论新项“位于”何处,都称为其范围。范围是该元素如何与系统中的所有其他元素不同。如果你有两个类,每个类都名为 ,但一个在名为 的包中定义,另一个在名为 的包中定义,你可以非常确定它们不是一回事。它们的范围表明它们是不同的类,永远不要混淆。此原则适用于任何具有名称的封闭内容,并在其中定义新内容。MenuGraphicalUIElementsRestaurantNecessities

Whenever you declare a variable, define a method, or describe a class, whatever that new item “lives” in is called its scope. The scope is how that element is made unique from all others in the system. If you have two classes, each named Menu, but one is defined in a package named GraphicalUIElements and the other is defined in a package named RestaurantNecessities, you can be pretty sure that they’re not the same thing. Their scope indicates that they are distinct classes, never to be confused. This principle applies any time there is an enclosing something that has a name and within which you define something new.

类是它们定义的方法和字段的范围。命名空间和包是它们内部任何内容的作用域。方法和函数是其中声明的局部变量的范围。我们一次又一次地看到相同的机制在许多不同的语言特性中起作用。要访问特定元素,我们通过提供自上而下的作用域来精确说明我们想要的元素。不幸的是,事情并不总是这么简单。有时这些范围是隐式的,我们可以将它们关闭,例如在引用方法中的局部变量或类中的其他成员时。此外,尽管这些范围的行为很常见,但它们可以具有不同的访问语法。例如,在 C++ 中,要访问命名空间中的类,我们会使用双冒号将其表示为 。但是,给定该类的实例,我们将使用点运算符将其菜单项列表作为 访问。这两种技术都指定了我们希望从范围元素中选择的元素,但它们以不同的方式实现。本文定义了一种处理所有范围的单一方式,无论它是如何针对特定语言中的特定语言功能实现的。MenuGraphicalUIElementsGraphicalUIElements::MenuaMenu.theItems

Classes are scopes for the methods and fields they define. Namespaces and packages are scopes for anything inside of them. Methods and functions are scopes for the local variables declared within them. Over and over again we see the same mechanism at work in many different language features. To access a specific element, we state precisely which one we want by providing the scopes from the top down. Unfortunately, it isn’t always this simple. Sometimes these scopes are implicit, and we can leave them off, such as when referring to a local variable within a method or to another member within a class. Furthermore, despite their common behavior, these scopes can have different syntaxes through which they are accessed. For instance, in C++, to access the Menu class within the GraphicalUIElements namespace, we would state it as GraphicalUIElements::Menu, using a double colon. Given an instance of that class, however, we would access its list of menu items as aMenu.theItems, using a dot operator. Both of these techniques specify which element we wish to select from a scoping element, but they do so in different ways. This text defines a single way in which we can treat all scoping, regardless of how it is implemented for specific language features within specific languages.

考虑具有 method 的 class 和具有 method 的 class ,如图 2.4 中的 UML 和程序清单 2.1 中的代码所示。 具有类型为 的字段。在正文中,实例化 type 的实例,然后调用。该方法的范围由 object .在面向对象语言中,函数和方法总是被某个范围括起来,即使该范围是隐式的。AfBgAbBmain()aAa.f()f()a

Consider class A that has a method f, and class B that has a method g, as shown in UML in Figure 2.4 and in code in Listing 2.1. A has a field b of type B. In the main() body, an instance a of type A is instantiated, and then a.f() is invoked. The method f() is scoped by the object a. In object-oriented languages, functions and methods are always enclosed by some scope, even if the scope is implicit.

图像

图 2.4.作为 UML 的简单方法调用。

Figure 2.4. A simple method call as UML.

即使在 C++ 中,也可以将全局函数和字段视为它们驻留在表示全局命名空间的不可见隐式对象中,这就是运行时有效处理它们的方式。请查看 2011 年 C++ ISO 标准工作草案 [6] 的第 3.3.6 条 [basic.scope.namespace] 第 3 段进行确认。此外,传统观点现在不鼓励在 C++ 中使用文件范围的元素,即那些声明为 global 和 static 的元素,而支持未命名的命名空间,它们更明确地执行相同的功能。换句话说,每个翻译单元都有自己的对象进行范围界定,而范围没有名称的事实意味着它不能在该翻译单元之外使用。同样,对象被用于限定和包装以前被视为自由漫游的元素。

Even in C++ global functions and fields can be considered as though they reside inside an invisible and implicit object representing a global namespace, which is how the runtime effectively treats them. Check Clause 3.3.6 [basic.scope.namespace], paragraph 3, of the 2011 C++ ISO Standard Working Draft [6] for confirmation. Further, the use of file-scoped elements in C++, those declared global and static, is now discouraged by the conventional wisdom in favor of unnamed namespaces, which perform the same function more explicitly. In other words, each translation unit gets its own object for scoping, and the fact that the scope has no name means it can’t be used outside that translation unit. Again, objects are being used to scope and wrap elements that have previously been considered as freely roaming.

清单 2.1.作为伪代码的简单方法调用。

Listing 2.1. A simple method call as pseudocode.


 1 A 类 {

B b;

3 f() {

b.g();

5 };

};



7 B 类 {

9 g() {};

h() {};

11 };



13 main() {

A a;

15 a.f();

   };

 1 class A {

       B b;

 3     f() {

           b.g();

 5     };

   };

 7

   class B {

 9     g() {};

       h() {};

11 };



13 main() {

       A a;

15     a.f();

   };


回到代码示例,依次调用 ,并且这两个方法之间的关系退出。和 (enclosure 或 scoping 之一)之间的关系是上下文相关的。它有助于指定我们正在谈论的方法。和 之间存在类似的关系。但是,从 to 的调用不仅仅是上下文的,它是这两种方法之间的主要关系。换句话说,这是我们感兴趣的关系,但我们必须使用范围界定来实现这一点。a.f()b.g()afbga.f()b.g()

Getting back to the code example, a.f() in turn calls b.g(), and a relationship exits between those two methods. The relationship between a and f, one of enclosure or scoping, is contextual. It helps specify which method we are talking about. A similar relationship exists between b and g. The call from a.f() to b.g(), however, is not just contextual, it is the primary relationship between those two methods. In other words, this is the relationship we’re interested in, but we have to use scoping to get there.

界定关系范围有助于我们将视图细化为特定的设计元素。我们正好需要两个这样的设计元素来形成 EDP 中描述的单一关系。可能有许多范围界定关系构成了我们感兴趣的最终单一关系,但我们并不立即关心它们。它们是构成我们希望使用的关系端点的编程元素描述的一部分。回到我们之前对房屋风格和设计的隐喻,范围界定元素有点像说一块木头是一块 2 x 4 或半英寸厚的斜纹胶合板。这些信息并没有告诉我们如何将一个定位到另一个方向或将钉子推向何处。范围界定提供了一个上下文来帮助定义元素是什么,但它对解释该元素与系统中的其他元素的关系没有太大作用。

Scoping relationships help us refine our view to a particular design element. We need exactly two such design elements to form the single relationship described in an EDP. There may be a number of scoping relationships that set up the final single relationship we are interested in, but we are not immediately concerned with them. They are part of the description of the elements of programming that comprise the endpoints of the relationship we wish to work with. Returning to our earlier metaphor of housing styles and design, the scoping elements are a bit like stating that a piece of wood is a two-by-four or a half-inch-thick sheet of bias-grain plywood. That information doesn’t tell us how to orient one to the other or where to drive the nails. Scoping provides a context to help define what an element is, but it doesn’t do much to explain how that element relates to others in the system.

我们现在可以将我们是否已经达到分解目标的问题调整为,这是否体现了不止一种利益关系?,这使我们更接近我们的目标。现在我们只需要确定什么是利益关系。到目前为止,我们已经介绍了范围关系,其中包括方法和字段的类所有权,以及命名空间、包和编程中可用的所有其他分组技术,并且我们声明我们目前对它们不感兴趣。让我们考虑一下我们可以在哪些之间形成关系。

We can now tweak our question of whether we’ve reached our decomposition goal to, Does this embody more than one relationship of interest?, which gets us closer to our goal. Now we just have to determine what a relationship of interest is. So far, we’ve described scoping relationships, which include class ownership of methods and fields, as well as namespaces, packages, and all the other grouping techniques available in programming, and we stated that we’re not interested in them at the moment. Let’s consider instead what remains that we could form relationships between.

剩下的是类、它们的字段和方法,仅此而已。列表中似乎缺少一项:对象。毕竟,它是面向对象的编程!为了完整起见,我们将向列表添加对象,稍后我们将展示它们如何成为此方法的核心。(有关正式说明,请参阅附录。现在,我们的列表中有这四种编程实体:对象、方法、字段和类或类型。这似乎是一件奇怪的事情,但实际上是有道理的。

What remains are classes, their fields and methods, and little else. One item seems to be missing from the list: objects. It is object-oriented programming, after all! We’ll add objects to the list for completeness, and later we’ll show how they’re central to this approach. (For a formal explanation, see the appendix.) For now, we have these four kinds of programmatic entities on our list: objects, methods, fields, and classes, or types. This may seem like an odd thing to do, but it actually makes solid sense.

我们不称它们为类,因为并非每种面向对象语言都有类,但每种面向对象语言都有类型。请记住,我们希望以一种可以跨越语言边界的方式描述设计模式,因此我们将寻找每种面向对象语言的通用特性和需求。那么,我们如何从大多数人熟悉的类过渡到更经典的“纯”面向对象方法呢?它实际上并没有那么复杂:它只涉及将一个类拆分为其组成部分。

We don’t call them classes, because not every object-oriented language has classes, but every object-oriented language has types. Remember that we’re looking to describe design patterns in a way that can cross language borders, so we’re going to be looking for features that are common to, and a requirement for, each and every object-oriented language. So how do we make the transition from classes, which most people are familiar with, to a more classically “pure” object-oriented approach? It’s not that complex, really: it simply involves splitting a class into its constituent parts.

类是一头有趣的野兽,因为它在大多数面向对象语言中非常常见,以至于许多学生和开发人员都认为它是主要和必要的元素。从严格的面向对象的角度来看,现实情况并非如此。某些语言(如 Self 和 Lua)甚至没有 class 的概念。相反,它们依赖于对对象进行原型设计、克隆和其他操作来执行相同的功能。在大多数语言使用 class 的地方,不仅仅是深奥的语言表现出这种对象用法,因为在 JavaScript 中可以看到各种这种用法。即使是 Smalltalk,可以说是大多数面向对象语言的鼻祖,即使使用相同的术语,对类的实现和理解也大不相同。

A class is an interesting beast in that it is so common in most object-oriented languages that many students and developers consider it a primary and necessary element. The reality, from a strictly object-oriented viewpoint, is that it is not. Some languages, such as Self and Lua, don’t even have the concept of a class. Instead, they rely on prototyping, cloning, and other actions on objects to perform the same functions. It’s not just esoteric languages that exhibit this use of objects in places where most languages use classes, as a variety of this usage can be seen in JavaScript. Even Smalltalk, arguably the progenitor of most object-oriented languages, has a quite different implementation and understanding of a class even though the same term is used.

通常,当前常见的面向对象语言(如 C++ 和 Java)中的类执行两项任务。首先,它描述将位于该类的实例化对象中的元素、成员方法和字段。我们大多数人都希望以这种方式使用类。其次,它描述了该类的所有实例化对象、方法和字段中共有的元素。这就是在 C++ 或 Java 中将方法或字段声明为 .第一个用例直接对应于正式意义上的类型,第二个用例可以使用特殊用途的对象重新创建。考虑在 C++ 的类中声明为 static 的字段,以及如何访问它,如清单 2.2 所示。static

In general, a class in current common object-oriented languages such as C++ and Java performs two duties. First, it describes the elements that will be in instantiated objects of that class, the member methods and fields. Most of us expect a class to be used in this way. Second, it describes elements that are common across all instantiated objects of that class, the class methods and fields. This is what happens when, in C++ or Java, we declare a method or field to be static. The first use case corresponds directly to a type in the formal sense, and the second use case can be re-created using a special-purpose object. Consider a field declared static in a class in C++ and how it is accessed, as in Listing 2.2.

清单 2.2.类、实例和命名空间中的字段,如 C++ 中定义和使用的字段。

Listing 2.2. Fields within classes, instances, and namespaces, as defined and used in C++.


   NamespaceMyNamespace {

2 int aField;

};



4 class MyClass {

6 public:

static int sharedField;

8 int instanceField;

};

10

main() {

12 MyClass mc;

mc.instanceField = 0;

14 MyClass::sharedField = 1;

MyNamespace::aField = 2;

16 };

   namespace MyNamespace {

 2     int aField;

   };

 4

   class MyClass {

 6 public:

       static int sharedField;

 8     int instanceField;

   };

10

   main() {

12     MyClass mc;

       mc.instanceField = 0;

14     MyClass::sharedField = 1;

       MyNamespace::aField = 2;

16 };


请注意,在使用 class 字段时,以 class name 开头。这与用于访问定义的 .我们可以将包含类的类共享元素的实体视为“活动”对象,也可以以相同的方式将命名空间视为命名空间。我们提供对象的名称和我们想要从中选择的部分,就像以正常方式从 class 实例化的任何对象一样。当语言提供命名空间、包或类时,它提供了一种自动实例化这些对象的方法。mainsharedFieldMyClassaFieldmyNamespace

Notice that in main the use of the class field sharedField is prefaced by the class name MyClass. This is exactly the same notation used to access aField in the defined myNamespace. We can think of the entity that holds the class-shared elements of a class as a “live” object, and we can think of a namespace in the same way. We provide the name of the object and the piece we want selected out of it, just like any object that was instantiated from a class in the normal way. When the language provides a namespace, a package, or a class, it is providing a way of instantiating these objects automatically.

还是不相信?在 Smalltalk 中,类由对象表示。从字面上看,它被命名为类对象。为了实例化该类的对象,我们向该类对象发送一条消息(Smalltalk 相当于 “call a method of the object” ),以返回该类的实例。类方法和字段位于该类对象中。这几乎与我们在这里描述的场景完全相同。

Still not convinced? In Smalltalk, the class is represented by an object. Literally, it is named the class object. To instantiate an object of that class, we send the class object a message—the Smalltalk equivalent for “call a method of the object”—to return an instance of that class. Class methods and fields live in that class object. It is almost exactly the scenario we are describing here.

或者,看看 Java。在 Java 中,类对象是一个特殊对象,可通过反射进行检查,并与开发人员定义的类相关联。与 Smalltalk 不同,在 “正常” 使用中,开发人员不会直接访问它,而是通常保留用于更复杂的反射操作。与 Smalltalk 中的基本原则相同,只是从 C++ 借用的结构将进程隐藏在一点语法糖下。new MyClass

Or, look at Java. In Java the class object is a special object that is available for inspection through reflection and is associated with a class that a developer defines. Unlike in Smalltalk, it is not directly accessed by the developer in “normal” use but is usually reserved for more complex reflection actions. The same basic principle applies as in Smalltalk, except that the new MyClass construct borrowed from C++ hides the process under a bit of syntactic sugar.

清单 2.3 在上半部分显示了一个 Java 片段,在下半部分显示了该类的一个可能分解。如果您将 usually 视为在这种情况下的同义词,然后通过 instead 访问静态成员,如 in 或 ,那么它与您可能已经习惯的 Java 没有太大区别。类似的机制可用于 C++,其中不存在反射选项。new MyClassMyClass_Object.new()MyClass_ObjectMyClass_Object.sharedDataMyClass_Object.sharedMethod()

Listing 2.3 shows a Java snippet in the top half and one possible decomposition of the class in the bottom half. If you think of the usual new MyClass as being a synonym for MyClass_Object.new() in this case, and then access to the static members being done through the MyClass_Object instead, as in MyClass_Object.sharedData or MyClass_Object.sharedMethod(), then it’s not that different than what you may be used to in Java already. Similar mechanisms are available for C++, where no reflection option exists.

清单 2.3.一个 Java 类和一个可能的等效对象和类型。

Listing 2.3. A Java class, and one possible equivalent object and type.


   MyClass {

2 public:

static int sharedData;

4 int instanceData;

static void sharedMethod() { ... };

6 void instanceMethod() { ... };

};





8 10

MyClass:

12 int instanceData;

无效 instanceMethod();

14

MyClass_Object:

16 int sharedData;

无效 sharedMethod();

18 MyClass ();

   class  MyClass {

 2 public:

       static int sharedData;

 4     int instanceData;

       static void sharedMethod() { ... };

 6     void instanceMethod() { ... };

   };

 8



10

   MyClass:

12     int  instanceData;

       void     instanceMethod();

14

   MyClass_Object:

16     int sharedData;

       void    sharedMethod();

18     MyClass new();


尽管处理方式的细节因语言而异,但在每种情况下,都可以使用类型和对象来模拟类。虽然乍一听这听起来有点陌生,但它很快就会成为实践中的第二天性。请记住,没有静态成员的类(使用 C++ 和 Java 术语)只是一种类型。任何定义的类成员都将被移动到相应的类对象中,就像在 Smalltalk 中一样,或者移动到反射访问的 Java 类对象中。

Although the details of how it is handled will differ from language to language, in every case a class can be emulated using a type and an object. While this may sound a bit foreign to you at first, it quickly becomes second nature in practice. Just remember that a class with no static members (using the C++ and Java terminology) is just a type. Any defined class members would be moved into a corresponding class object, as in Smalltalk, or into the reflection-accessed Java class object.

我们需要类型来满足类型化语言的基本属性,而对象是面向对象语言的核心,无论它们的类特性如何。通过结合使用这两者,我们可以模拟基于类的面向对象语言的类。这允许我们将定义设计模式所需的项目集简化为仅之前提到的四个项目:对象、方法、字段和类型。

We needed types in order to satisfy basic properties of typed languages, and objects are the core of object-oriented languages regardless of their class features. By using these two in conjunction, we can emulate classes from class-based object-oriented languages. This allows us to simplify our set of items needed to define design patterns to just our four previously mentioned items: objects, methods, fields, and types.

这个简短列表中的项目可以以哪些方式相互作用?表 2.2 是左列中的实体与顶行中的实体交互的可能方式的完整列表。对象和类型可以包含并因此定义这四个元素中的任何一个,但是我们将在范围界定机制下从考虑中消除这些“包含”关系。在某些语言中,方法可以类似地定义或限定内部方法或类型,当然我们可以定义局部变量或字段。方法还可以调用其他方法,使用非本地字段,并具有返回类型。字段更简单,因为可以为它们分配方法或其他字段的返回值。当然,他们有一个类型。最后,类型可以通过范围定义四种实体类型中的任何一种,并且类型可以通过子类型化来依赖其他类型。

In what ways can the items on this short list interact? Table 2.2 is a complete list of possible ways in which the entities in the left column can interact with the entities on the top row. Objects and types can contain, and therefore define, any of the four elements, but we’re going to eliminate those “contains” relationships from consideration under the scoping mechanism. Methods can, in some languages, similarly define, or scope, inner methods or types, and of course we can define local variables or fields. Methods can also call other methods, use nonlocal fields, and have a return type. Fields are simpler in that they can be assigned the return value of a method or of another field. And, of course, they have a type. Finally, types can define through scope any of the four entity kinds, and types can rely on other types through subtyping.

表 2.2.面向对象编程的实体之间的所有交互

Table 2.2. All interactions between entities of object-oriented programming

图像

现在没有那么多交互是可能的,这意味着我们可以开始将它们列举为一组有限的可能性,更好的是,相当小的可能性。表 2.3 显示了不包括定义其他实体的交互。

Not that many interactions are possible now, which means that we can start enumerating them into a finite and, better yet, quite small set of possibilities. Table 2.3 shows the interactions that don’t include defining another entity.

表 2.3.面向对象编程的实体之间的非作用域交互

Table 2.3. Nonscoping interactions between entities of object-oriented programming

图像

字段具有它所依赖的类型;这只是字段的类型。这同样适用于对象。同样,方法具有返回类型。它们用于定义数据片段是什么,是字段还是其他对象,例如参数或方法的返回值。与范围界定非常相似,定义数据的感觉是描述对象或字段本身,而不是提供两个实体之间的关系。

A field has a type on which it relies; this is simply the type of the field. The same holds for objects. Similarly, methods have return types. They are used to define what a piece of data is, whether it is a field or another object such as a parameter or a return value from a method. Much like scoping, defining data has the feel of describing the object or field itself, not providing a relationship between two entities.

例如,请看 Listing 2.4 中的代码片段。数据成员定义为 类型的实例 。这告诉我们什么是,它的内部范围告诉我们如何解释它,但它没有告诉我们如何与系统中的其他元素交互。同样,该方法的参数类型只告诉我们这些参数是什么,而不是它们与其他任何事物的关系。posPositionposGlyphposscaleCopy

For instance, look at the code snippet in Listing 2.4. The data member pos is defined as being an instance of the type Position. This tells us what pos is, and its scoping within Glyph tells us some of how to interpret it, but it doesn’t tell us how pos interacts with other elements in the system. Similarly, the types of the parameters to the scaleCopy method only tell us what those parameters are, not how they relate to anything else.

另一方面,其他关系(例如,一个类型依赖于另一个类型)提供的信息不仅仅是一个元素的描述。对其他类型的类型依赖(通常被视为继承)有相当详细的文档记录。我们甚至可以说它是根深蒂固的和被理解的。

On the other hand, other relationships, such as a type relying on another type, provide more information than just a description of one element. Type reliance on other types, which is most often seen as inheritance, is rather well documented. We might even say it is ingrained and understood.

清单 2.4.键入作为上下文。

Listing 2.4. Typing as context.


  字形 {

2 位置 pos;

字形 scaleCopy( float x, float y );

4 }

  class Glyph {

2     Position pos;

      Glyph scaleCopy( float x, float y );

4 }


为了定义 EDP,表 2.3 的中心只剩下四个基本关系,其中方法或字段依赖于另一个方法或字段的值,以完成其工作,依此类推。这些关系是方法调用(方法依赖于方法)、方法使用的字段(方法依赖于字段)、方法设置的字段(字段依赖于方法进行状态更改)以及依赖于另一个字段(如.如上表所示,我们可以将这些称为方法调用、字段使用、状态更改和内聚。信不信由你,几乎整本书都只关注第一个,即方法调用 reliance。是的,如果从正确的角度来看,关于一个简单的 method call reliance 有很多话要说。a = b + 1

For the purpose of defining the EDPs, there are only the four basic relationships left in the center of Table 2.3, where either a method or a field relies on another method or field for its value, to get its job done, and so on. Those relationships are a method call (method relies on method), a field use by a method (method relies on a field), a field being set by a method (field relies on method for a state change), and a field relying on another field, such as a = b + 1. As shown in the preceding tables, we can call these a method call, a field use, a state change, and cohesion. Believe it or not, nearly this entire book concentrates on just the first one, a method call reliance. Yes, there is a lot to be said about a simple method call reliance when looked at in the right light.

您可能已经想到,方法调用看起来非常像一个结构实体,而不是一个概念实体。这是真的,这就是为什么我早些时候开始偷偷地使用依赖这个词。一个元素对另一个元素的依赖不需要直接连接。考虑一个在其主体中调用 method 的方法。我们说这依赖于 。现在假设 反过来调用另一个方法 ,如清单 2.5 所示。我们说这依赖于 。这种关系是传递的,这应该是不言而喻的 —— 如果依赖于 ,并且依赖于 ,那么也依赖于 。它不是直接依赖,但这并不重要:仍然依赖 to complete its work, so that can do it work that can do it work.fgfgghghfgghfhfhf

It has probably occurred to you that a method call looks an awful lot like a structural entity, not a conceptual one. This is true, and that’s why I started sneaking in the word reliance earlier. A reliance by one element on another doesn’t require a direct connection. Consider a method f that calls method g in its body. We say that f relies on g. Now assume that g in turn calls another method h, as in Listing 2.5. We say that g relies on h. It should be self-evident that this relationship is transitive—if f relies on g, and g relies on h, then f also relies on h. It’s not a direct reliance, but that doesn’t really matter: f still relies on h to complete its work so that f can do its work.

让我们稍微扭转一下。如果我们想依赖 ,是直接的方法调用还是方法在中间有关系吗?它没有。只要依赖于某种路径,我们的要求就成立。我们刚刚从结构连接(方法调用)跃升为概念连接(方法调用依赖)。这使我们不必以结构化的方式讨论设计的低级编程概念,并使我们能够以前面概述的概念方式描述它们,这对于使用 SPQR 处理和查找设计模式实例是必需的。我们将在第 4 章4.1.1 节中更详细地讨论这一点,并展示它如何构成使用 EDP 的关键部分。fhgfh

Let’s turn this around a bit. If we want f to rely on h, does it matter if it is a direct method call or if method g is in the middle? It does not. As long as f relies on h by some path, our requirement holds true. We just made the leap from a structural connection, that of a method call, to a conceptual connection, that of a method call reliance. This frees us from having to discuss low-level programming concepts of design in a structural manner and enables us to describe them in the conceptual way outlined earlier as necessary for working with and finding design pattern instances with SPQR. We discuss this in more detail and show how it forms a critical portion of working with EDPs in Chapter 4, Section 4.1.1.

在教授 SPQR 编程概念的过程中,我们摆脱了僵化的编程结构,而是获得了一种灵活而宽容的方式来描述构成软件设计的关系。通过我们自己使用这些相同的技术,我们为开发人员和设计人员提供了相同的灵活思维和能力,以有条不紊但易于理解的方式将设计从实现中抽象出来。我们从相同的方法中获得双重职责。

In the process of teaching SPQR about programming concepts, we made the transition away from rigid structures of programming and instead gained a flexible and forgiving way to describe the relationships that form software design. By using these same techniques ourselves, we provide developers and designers with the same flexible thinking and ability to abstract design from implementation in a methodical yet understandable manner. We get double duty out of the same approach.

清单 2.5.作为伪代码的方法调用链。

Listing 2.5. A method call chain as pseudocode.


   函数 f() {

2 g();

};



4 函数 g() {

6 h();

};



8 函数 h() {};

10

function main {

12 f();

   };

   function f() {

 2     g();

   };

 4

   function g() {

 6     h();

   };

 8

   function h() {};

10

   function main {

12     f();

   };


那么让我们回顾一下。我们已将有趣的编程元素列表缩减为四个:对象、方法、字段和类型(根据您的实现语言,您可以将其视为类)。其他编程实体(如命名空间、包等)可以帮助我们描述我们正在查看的元素,但它们不会创建我们在检查重要设计时关注的那种关系。在这四个元素中,实际上有四种关系或依赖性是我们最关心的:方法-方法、方法-字段、字段-方法和字段-字段。在本书中,我们几乎只关注方法-方法的依赖。

So let’s recap. We have reduced our list of interesting elements of programming to just four: objects, methods, fields, and types (which, depending on your implementation language, you may think of as classes). Other programmatic entities, such as namespaces, packages, and so on, help us describe which element we’re looking at, but they don’t create the kind of relationships we’re focused on when inspecting nontrivial design. Of those four elements, there are really four relationships, or reliances, that we’re most concerned with: method–method, method– field, field–method, and field–field. We concentrate almost exclusively on the method–method reliance in this book.

仅基于方法调用的这种依赖真的能帮助我们描述更大的设计模式文献吗?只要有合适的环境,一切皆有可能。

Can this one reliance, based solely on method calls, really do anything useful to help us describe the larger design pattern literature? Given the right context, anything is possible.

2.2.3. 上下文

2.2.3. Context

上一节是关于为我们的讨论找到合适的关系。我们专注于主要依赖,例如方法和字段之间的依赖,绕过了其他类型的关系,例如类型-类型依赖或继承。现在是时候将这些上下文关系重新带入讨论了。

The previous section was all about finding the right relationships for our discussion. We focused on primary reliances such as those between methods and fields, bypassing other kinds of relationships, such as type–type reliances or inheritance. Now it’s time to bring these contextual relationships back into the discussion.

对于给定的方法调用 reliance,我们可以处理其他三条信息来帮助我们弄清楚该 reliance 在特定设计中的目的是什么。如果你看一下方法调用,这些就很明显了,如图 2.5 所示。在面向对象的理论中,实际上没有简单的方法或函数这样的东西。这并不罕见。在 C++ 等面向对象的混合编程语言中,您可以将函数放在全局空间中。在更纯粹的语言(如 Java)中,你不能;每个方法都必须位于对象中,要么作为类的实例化,要么作为静态类级方法,这等效于将方法与该类的类对象放在一起。我们假设每个方法都必须嵌入到一个对象中。对于任何给定的方法调用依赖,都有四个部分:调用方法、被调用方法和各自所在的对象(类-对象或实例)。清单 2.6 为 C++ 中的此类方法调用设置了类和对象。图 2.5 说明了调用 p.g() 方法的四个部分 o.f()。这些部分存在于每个方法调用中。

For a given method call reliance, there are three other pieces of information we can work with to help us figure out what the purpose of that reliance is in a particular design. These are obvious if you look at a method call, as shown in Figure 2.5. In object-oriented theory, there’s really no such thing as a plain method or function. This isn’t that unusual. In hybrid object-oriented programming languages such as C++, you can put functions in the global space. In more pure languages such as Java, you cannot; each method must be in an object, either as an instantiation of a class or as a static class-level method, which is equivalent to placing the method with the class-object for that class. Let’s assume that every method must be embedded within an object. For any given method call reliance, then, there are four pieces: the calling method, the called method, and the object (class-object or instance) each resides in. Listing 2.6 sets up the classes and objects for such a method call in C++. Figure 2.5 illustrates the four pieces of the method o.f() calling the method p.g(). These pieces are present in every method call.

图像

图 2.5.方法调用的各个部分。

Figure 2.5. The parts of a method call.

清单 2.6.图 2.5 的简单方法调用。

Listing 2.6. Simple method call for Figure 2.5.


 1 class Class2 {

void g() {};

3 };



5 class Class1 {

void f() {

7 Class2 p;

p.g() 的;

9 }

};

11

void main() {

13 Class1 o;

o.f() 的;

15 };

 1 class Class2 {

       void g() {};

 3 };



 5 class Class1 {

       void f() {

 7         Class2 p;

           p.g();

 9     }

   };

11

   void main() {

13     Class1 o;

       o.f();

15 };


隐藏在众目睽睽之下的三条信息是:

The three pieces of information hiding in plain sight are:

1. 封闭对象之间的相似性

1. The similarity between the enclosing objects

2. 封闭对象类型之间的相似性

2. The similarity between the types of the enclosing objects The

3. 调用方法和被调用方法的相似性

3. similarity between the calling method and the called method

我在这里引入了一个新词:相似性。通俗地说,它的意思是你的想法——两件事之间的相似之处。但是,在方法调用依赖的上下文中,这意味着什么呢?

I’ve introduced a new word here: similarity. Colloquially, it means what you think—a resemblance between two things. But what does it mean in the context of method call reliance?

对象相似性是一个对象与另一个对象的相似程度。是同一个物体吗?它是否通过指针指向第二个对象的别名?它完全无关吗?

Object similarity is the extent to which one object is like another. Is it the same object? Is it an alias to the second object through a pointer? Is it completely unrelated?

我们还可以讨论对象类型之间关系的相似性。它们是同一类型吗?一个是另一个的子类型吗?或者,也许一个是另一个的兄弟类型,具有共同的超类型祖先?

We can also discuss the similarity of relationship between the types of the objects. Are they the same type? Is one a subtype of the other? Or maybe one is a sibling type of the other, with a common supertype ancestor?

方法相似性有点棘手。我们需要确定两种方法是否正在尝试执行类似的任务。我们可以查看许多方面:也许我们可以在 Comments 中映射关键字,或者对方法体进行全面分析以确定它们在计算上完成什么。但是,如果我们从社会工程学中汲取经验,查看方法名称,并在较小程度上查看方法签名,则有一种更简单的方法。

Method similarity is a bit trickier. We need to determine whether two methods are trying to do a similar task. We could look at many aspects: we could map keywords in comments, perhaps, or do a full analysis of the method bodies to determine what they computationally accomplish. There is a much easier way, however, if we take a page from social engineering and look at the method names and, to a lesser extent, the method signatures.

不,真的。想一想。开发人员传达方法意图的主要方式是什么?名称。这甚至体现在肯特·贝克 [5] 的 Intention Revealing Selector 模式中(强调是我的):“在命名方法中,你有两个选择。第一种是按照方法如何完成任务来命名方法。反对这种命名风格的最重要论点是它不能很好地传达......第二种选择是在方法应该完成的任务之后命名方法,并将 “how” 留给各种方法主体。这是一项艰巨的工作,尤其是当您只有一个实现时。你的脑海里充满了你将如何完成任务,所以名字跟着怎么做是很自然的。将方法的名称从 “how” 移动到 “what” 的努力是值得的,无论是长期的还是短期的。生成的代码将更易于阅读且更加灵活。Beck 将其总结为,“以方法完成的任务命名方法”。

No, really. Think about it for a moment. What’s the primary means by which a developer conveys the intent of a method? The name. This is even ensconced in Kent Beck’s [5] Intention Revealing Selector pattern (emphasis mine): “You have two options in naming methods. The first is to name the method after how it accomplishes its task. . . . The most important argument against this style of naming is that it doesn’t communicate well. . . . The second option is to name a method after what it is supposed to accomplish and leave “how” to the various method bodies. This is hard work, especially when you only have a single implementation. Your mind is filled with how you are about to accomplish the task, so it’s natural that the name follow how. The effort of moving the names of method from “how” to “what” is worth it, both long term and short term. The resulting code will be easier to read and more flexible.” Beck sums this up as, “Name methods after what they accomplish.”

Robert Martin 的 Clean Code [27] 用了一整章的篇幅来单独讨论命名,因为它非常重要。也许这句话最好地用那段经文对一句老话的引用来概括:“说出你的意思。你说的就是认真的。这只是很好的做法,虽然不是每个开发人员组都会虔诚地遵循它,但它很常见,可以利用它来为我们节省大量的辛勤工作。在最坏的情况下,我们可以依靠同义词查找和单词重新排序来实现这一点,并且在意图上很接近,但在大多数情况下,简单的词典比较的频率相当令人震惊。makeAStringstringCreator

Robert Martin’s Clean Code [27] spends an entire chapter on naming alone, because it’s so important. Perhaps this can be best encapsulated using that text’s invocation of the old adage “Say what you mean. Mean what you say.” This is just good practice, and although not every developer group will follow it religiously, it is common enough that it can be leveraged to save us a tremendous amount of hard work. At worst, we can fall back on synonym lookups and word reordering for realizing that makeAString and stringCreator are close in intent, but it’s rather shocking how often a simple lexicographic comparison will do in most cases.

重载的方法,那些名称相同但参数列表不同的方法,提供了一个小问题,但同样,我们可以看看人类开发人员如何推理相似性来寻找线索。与名称完全不同的方法相比,方法更类似于名称相同但参数列表不同的方法。完美匹配是具有相同名称和相同参数列表的匹配。然而,为了本文的目的,我们可以把这个细节放在一边。

Overloaded methods, those with the same name but different argument lists, offer a small hiccup, but again, we can look to how human developers reason about similarity for clues. A method is more similar to one with the same name but a different argument list than it is to a method with a completely different name. A perfect match is one with the same name and the same argument list. For the purposes of this text, however, we can set aside this detail.

SPQR 使用的这种算法具有非常好的结果。我最初选择该算法只是因为它最容易实现,我从没想过它真的有效。在初始测试后可以选择合适的方法之前,它只是一个占位符。一旦 SPQR 开始生成数据并且很明显这种方法在大多数情况下就足够了,必须找出原因。回想起来,这是显而易见的,但事后看来,意想不到的发现通常会更清晰。

This algorithm is what SPQR uses with stunningly good results. I originally selected the algorithm solely because it was the simplest to implement, and I never expected it to actually work. It was to be only a placeholder until an appropriate approach could be selected after initial testing. Once SPQR started producing data and it became obvious that this method was sufficient for most cases, the reason why had to be worked out. It’s obvious in retrospect, but then, unexpected discoveries usually are clearer in hindsight.

我们不应该对这种方法奏效感到惊讶。当开发人员阅读代码以试图确定其目的时,他或她做的第一件事就是查看事物的名称,作为它们做什么的线索。这就是 Beck 的命名原则 - 我们期望事物的命名是恰当的,而且更确切地说,是一致的。我们希望执行类似功能的事物具有相似的名称。我们在这里所做的只是利用这种期望。SPQR 只是被教导做我们在阅读代码时都会做的事情:尝试确定事物之间的关系。反过来,该经验通过更好地了解人类如何执行该任务并提供一种简单自然的方式来描述我们的第三个相似性,从而指导了 EDP 目录的形成。

We shouldn’t have been surprised that this approach worked. When a developer reads code to try to ascertain its purpose, the first thing he or she does is look at the names of things as clues to what they do. This is Beck’s naming principle at work– we expect things to be named appropriately, and more to the point, consistently. We expect things that perform similar functions to have similar names. All we’re doing here is leveraging that expectation. SPQR was simply taught to do the same thing we all do when reading code: to try to ascertain how things relate to each other. In turn, that experience guided the formation of the EDP catalog by providing a better understanding of how humans perform that task and offering a simple and natural way to describe our third similarity.

因此,对于任何方法调用,我们都可以通过一些相当简单的分析(无论是自动的还是手动的)来查看所涉及的对象、这些对象的类型以及方法的相似性。这给我们带来了什么?

For any method call, we can therefore look at the objects involved, the types of those objects, and the similarity of the methods through some fairly simple analysis, whether automated or manual. What does this give us?

它为我们提供了三个独立的上下文轴,用于放置和描述任何方法调用。我们可以把这三个轴看作是一个我们可以开始探索的三维空间。根据我们三个上下文的相似性评估,任何方法调用都将位于此空间内的一个位置。换句话说,所有共享同一空间的方法调用依赖项都具有相似的形式,我们可以独立于编写者、编写方式、它们出现在的软件系统中,甚至用于实现它们的语言来描述这些形式。

It gives us three independent axes of context in which to place and describe any method call. We can think of these three axes as defining a three-dimensional space that we can start to explore. Any method call will, depending on the similarity assessments for our three contexts, sit in exactly one spot within this space. In other words, all method call reliances that share the same space have a similar form, and we can describe those forms independently of who wrote them, how they were written, the software system they appear in, or even the language used to implement them.

我们可以根据它们为我们做什么以及我们在使用它们时的意图来描述依赖。我们可以讨论何时最好地使用它们,它们如何与该 3D 空间中周围的其他依赖相关联,以及如何从一种方法调用依赖转换为另一种方法调用依赖。

We can describe the reliances on the basis of what they do for us and what we intend when we use them. We can discuss when they’re best used, how they relate to other reliances around them in that three-dimensional space, and how to transform from one method-call reliance to another.

这为我们提供了第一组 EDP。

This gives us the first group of the EDPs.

2.2.4. 设计空间

2.2.4. The Design Space

继续我们的方法调用 reliance,让我们探索一下这个三轴定义的空间可能是什么样子。我们有三种正交且独立的方法来描述基于三种关系的单个方法调用:包含方法的对象、这些对象的类型以及方法本身。

Sticking with our method call reliance, let’s explore what this three-axis-defined space might look like. We have three orthogonal and independent ways of describing a single method call based on three relationships: the objects that enclose the methods, the types of those objects, and the methods themselves.

这似乎有很多需要记住的地方,但很容易想象。为了简单起见,我们可以从方法调用开始,其中我们忽略了对象类型关系,而是专注于对象和方法的相似性,如图 2.6 所示。

This may seem like a lot to remember, but it’s easy enough to visualize. For simplicity, we can start with method calls in which we ignore the object type relationship, instead concentrating on the object and method similarities, as in Figure 2.6.

图像

图 2.6.一个简单的设计空间。

Figure 2.6. A simple design space.

我们有一个 3×3 的网格,并且沿每个轴分别显示相似、未知不同的位置。包括 Unknown 的原因将很快解释。很明显,中心方块只是一个普通的 vanilla 方法调用。我们对所涉及的对象或方法一无所知。令人惊讶的是,这是大多数软件分析和研究的地方。当有人谈论方法调用时,没有指出方法、它们的封闭对象或这些对象的类型之间的关系,那么所有这些方法调用都位于一个正方形中。他们所有人。

We have a three-by-three grid and, along each axis, positions for similar, unknown, and dissimilar. Unknown is included for reasons that will be explained shortly. It should be pretty obvious that the center square is just a plain, vanilla method call. We don’t know anything about the objects or the methods involved. Surprisingly, this is where most software analysis and research lives. When someone talks about method calls without indicating a relationship between the methods, their enclosing objects, or the types of those objects, then all of those method calls live in that one square. All of them.

在顶部中心方块中,我们有对象相似的方法调用。为了进行本次讨论,我们就把这个相似性称为对象等价。这些是单个对象中的方法调用,称为对象的内聚,或者它与自己的方法的紧密绑定程度 [42]。

In the top center square, we have method calls with object similarity. For the purposes of this discussion, let’s just call that similarity object equivalence. These are method calls within a single object, known as an object’s cohesion, or how tightly it is bound with its own methods [42].

在底部中心方块中,我们有不同的对象,或者对于本次讨论,不同的对象实例,方法调用构成了耦合的基础,耦合是对象之间绑定的度量 [15]。

In the bottom center square, where we have dissimilar objects, or for this discussion, different object instances, method calls form the basis for coupling, a measure of the binding between objects [15].

顶部和底部的中心方块共同代表了大量研究的核心 [79,13,16,22,23,31,34],它与原始的中心方块一起,只构成了我们定义的空间的三分之一。其他三分之二是什么?最有趣的插槽是四个角的插槽,在那里我们既了解对象又了解方法。

The top and bottom center squares together represent the core of a wide body of research [79,13,16,22,23,31,34], which, along with the original center square, form just one-third of the space we’ve defined. What is in the other two-thirds? The most interesting slots are those in the four corners, where we have knowledge of both the objects and the methods.

图 2.7 用与它们相关的编程概念的名称填充了这四个角。例如,从对象到自身、同一方法的方法调用就是我们都知道的递归。我们可以按照 Listing 2.7 中的方式编写它,递归发生在 中。这很容易而且很明显,但其他三个呢?countDown()

Figure 2.7 fills out these four corners with the names of the programming concepts associated with them. For instance, a method call from an object to itself, to the same method, is what we all know as Recursion. We can write this as in Listing 2.7, with recursion occurring within countDown(). That one’s easy and obvious, but what about the other three?

图像

图 2.7.带有 EDP 的简单设计空间。

Figure 2.7. A simple design space with EDPs.

清单 2.7.Java 中的 Recursion 方法调用示例。

Listing 2.7. Example of a Recursion method call in Java.


1 class Timer {

public void countDown(int counter) {

3 if (counter > 0) {

this.countDown(counter--);

5 } else {

暂时

忽略此分支7 }

};

9 };

1 class Timer {

      public void countDown(int counter) {

3         if (counter > 0) {

              this.countDown(counter--);

5         } else {

              // Ignore this branch for now

7         }

      };

9 };


清单 2.8.C++ 中的 Delegation 方法调用示例。

Listing 2.8. Example of a Delegation method call in C++.


 1 class VicePresidentOfSales {

public:

3 void increaseQuarterlySales();

};



5 CEO {

7 VicePresidentOfSales vpOfSales;

void increaseProfits() {

9 vpOfSales.increaseQuarterlySales();

};

11 };

 1 class VicePresidentOfSales {

   public:

 3     void increaseQuarterlySales();

   };

 5

   class CEO {

 7     VicePresidentOfSales vpOfSales;

       void increaseProfits() {

 9         vpOfSales.increaseQuarterlySales();

       };

11 };


在对面的角落,我们看到 Delegation,这是一个包罗万象的词,在软件设计中有多种用途。在这里,我们给它一个精确的定义,表示两个不同对象之间以及不同方法之间的方法调用。示例 2.8 中所示。为什么使用“委派”一词?因为调用方法将其部分工作委托给另一个对象中的另一个方法。它在说,“去做这个,这样我就可以完成我的工作了。它正在分发一部分工作,这些工作是它自己预期任务的一部分,但与它自己的预期任务不同。想想一家公司的 CEO 委托负责各个部门的副总裁。首席执行官的工作被描述为“经营公司”,但每个 VP 的工作都以不同的方式描述:“管理人力资源”、“确保财务忠诚”、“调查和部署 IT 中的技术”。CEO 要求每个 VP 做一项看起来与他自己的任务完全不同的任务,但每个任务对 CEO 成功完成他的工作都至关重要。

In the opposite corner, we see Delegation, which is a catch-all word used in a number of ways in software design. Here we give it a precise definition to mean a method call between two distinct objects and between dissimilar methods. An example is shown in Listing 2.8. Why use the word delegation? Because the calling method is delegating part of its work to another method in another object. It’s saying, “Go do this so I can get my work done.” It’s doling out a portion of its work that is part of, but unlike, its own intended task. Think about the CEO of a company delegating to the vice presidents in charge of various departments. The CEO has a job described as “run the company,” but each VP has a job described in different ways: “manage HR,” “ensure financial fidelity,” “investigate and deploy technologies in IT.” The CEO is asking each VP to do a task that looks nothing like his own, but each is critical to the CEO successfully completing his job.

将此与右下角标记为 Redirection 的 R 进行对比。调用方法现在要求另一个对象执行一些工作,但它要求完成与方法本身应该执行相同的工作。它正在将其部分工作负载重定向到另一个对象,但该部分看起来与它自己的整体任务非常相似。示例 2.9 中所示。这有点像一条装配线,在特定任务期间并行处理多个单元,然后将结果汇集回一条主线上,继续到下一个工位。想象一下,您的公司正在制造汽车,而您负责给它们喷漆。然而,较长的干燥时间意味着如果您自己按顺序进行,您将耽误其他人。您不是自己一次一个地完成所有工作,而是雇用几个团队并为每个团队设置喷漆站。现在,您负责在每辆车完成前一辆汽车时将汽车路由到每辆车。你的工作是确保汽车被喷漆。他们的工作是确保汽车被喷漆。他们的工作是你的一个子集,看起来非常相似。您已经承担了路线规划责任,但这只是完成主要任务的一部分:确保汽车被喷漆。路由是一个实现细节,你和你的下属之间工作的相似性是使这种类型的工作分配重定向而不是委派的原因。重定向委托共同构成了如何耦合对象的基础。

Contrast this with the bottom right corner, labeled Redirection. The calling method is now asking another object to do some work, but it is asking for the same work to be done that the method itself is supposed to do. It is redirecting part of its workload to another object, but that part looks a lot like its own overall task. An example is shown in Listing 2.9. This is a bit like an assembly line branching out to work on multiple units in parallel during a particular task, then bringing the results back together onto one main line to continue to the next station. Imagine that your company is building cars, and you’re in charge of painting them. The long drying time, however, means that you would be holding up everyone else if you did them yourself in sequence. Instead of doing them all yourself, one at a time, you hire several teams and set up paint stations for each of them. Now you’re in charge of routing the cars to each one as they complete the previous one. Your job is to ensure the cars are painted. Their jobs are to ensure the cars are painted. Their job is a subset of yours that looks awfully similar. You’ve taken on a routing responsibility, but that’s only as part of completing your primary task: ensure the cars are painted. The routing is an implementation detail, the similarity in work between you and your subordinates is what makes this type of parceling out of work redirecting as opposed to delegating. Together, Redirection and Delegation form the basics of how objects can be coupled.

清单 2.9.Objective-C 中的 Redirection 方法调用示例。

Listing 2.9. Example of a Redirection method call in Objective-C.


 1 @interface Painter {}

-(void) paintCar: (Car) theCar;

3 @end



5 @interface PaintShopManager {

Painter subpainter;

7 }

-(void) paintCar: (Car) theCar;

9 @end



11 @implementation PaintShopManager

-(void) paintCar: (Car) theCar {

13 [subpainter paintCar:theCar];

}

15 @end

 1 @interface Painter {}

       -(void) paintCar: (Car) theCar;

 3 @end



 5 @interface PaintShopManager {

       Painter subpainter;

 7 }

       -(void) paintCar: (Car) theCar;

 9 @end



11 @implementation PaintShopManager

       -(void) paintCar: (Car) theCar {

13         [subpainter paintCar:theCar];

       }

15 @end


这将只留下左上角,即标记为 Conglomeration 的那个。这是递归的另一面,它们共同定义了内聚的支柱。集合是将不同的元素整合到一个整体中的操作 — 在本例中,将看起来不太像主任务的子任务(如在 Delegation 中)放在一起,但在这里只有一个对象完成工作。没有其他对象可以委托或重定向工作,因为这是一个单对象任务,但它被分解为更小的部分,这些部分由一个对象中的不同方法处理。这样做可能是为了可读性、灵活性,或者通常是在定义原子行为后的某个时间将原子行为编组为更复杂的行为。无论如何,它涉及一个对象和不同的方法,如示例 2.10 所示,它重新访问了示例 2.7 中的例子。Timer

This leaves just the top left corner, the one marked Conglomeration. This is the flip side of Recursion, and together they define the backbone of cohesion. Conglomeration is the act of bringing together disparate elements into a whole—in this case, pulling together subtasks that look little like the main task (as in Delegation), but here just one object does the work. There are no other objects to delegate or redirect work to because this is a one-object task, but it is broken down into smaller portions that are handled by different methods within the one object. This may be done for readability, flexibility, or, as is often the case, marshaling together atomic behaviors into a more complex one some time after the atomic ones were defined. In any case, it involves one object and disparate methods, as shown in Listing 2.10, which revisits our Timer example from Listing 2.7.

这四个基本概念,递归委派重定向聚集,是四个 EDP。是的,它们很简单。是的,它们是程序员每天反射性地使用的模式,但这就是重点,不是吗?这些使用是自反的,而不是有预谋的,并且每个使用都会对以后的实现和设计问题产生影响。如果你是编程新手,这些概念不是自反的,还没有根深蒂固的,你可能会对它们有疑问。“我什么时候使用它们?”“它们有什么用?”“还有哪些其他概念是相关的?”这正是设计模式被创造来表达和传递的部落智慧类型。请查阅本章后面目录中的 EDP 条目,了解有关这四个 EDP 的更详细讨论。即使您是一位经验丰富的专业人士,也要这样做——那里的功能可能比您最初想象的要多。

These four basic concepts, Recursion, Delegation, Redirection, and Conglomeration, are four EDPs. Yes, they’re simple. Yes, they’re patterns that programmers use every day by reflex, but that’s rather the point, isn’t it? The uses are reflexive, not premeditated, and each has consequences for later implementation and design issues. If you’re new to programming, these concepts are not reflexive, not yet ingrained, and you may have questions about them. “When do I use them?” “What are they good for?” “What other concepts are related?” This is exactly the type of tribal wisdom that design patterns were created to express and pass along. Look ahead to the EDP entries in the catalog following this chapter for more detailed discussions of these four EDPs. Do so even if you’re a seasoned pro—there’s more there than you may first think.

清单 2.10.Java 中的 Conglomeration 方法调用示例。

Listing 2.10. Example of a Conglomeration method call in Java.


 1 Timer {

void goDing();



3 public void countDown(int counter) {

5 if (counter > 0) {

这次忽略这个分支

7 } else {

this.goDing();

9 }

};

11 };

 1 class Timer {

       void goDing();

 3

       public void countDown(int counter) {

 5         if (counter > 0) {

               // Ignore this branch this time

 7         } else {

               this.goDing();

 9         }

       };

11 };


网格中的最后两个条目,其中对象相似性未知但方法相似性未知,该怎么办?好吧,在这一点上没有人很确定。如果您能想到这两个空间解决了什么编程概念或设计问题,那么您就有机会添加到 EDP 集合中。毕竟,这并非没有先例。随着开发人员偶然发现使用已建立部分的新方法,一些方法将被视为有用,并将开发语义。这些新技术如果足够有用,将不可避免地成为下一代语言的主要语言功能。您可能是帮助定义它们的人。同时,让我们删除现有研究所在的中心列和中心行,我们还不确定该如何处理它。这样,我们在 2×2 的网格中留下了四个条目,如图 2.8 所示。

What about the final two entries in the grid where the object similarity is unknown but the method similarity is? Well, no one’s quite sure at this point. If you can think of what programming concept or design issue is addressed by those two spaces, there’s an opportunity for you to add to the EDP collection. This isn’t without precedent, after all. As developers stumble on new ways of using established pieces, some ways will be seen as useful and will develop semantics. These new techniques, if useful enough, inevitably end up in the next generation of languages as primary language features. You might be the person who helps define them. In the meantime, let’s remove the center column, where existing research sits, and the center row, which we’re really not sure what to do with yet. This leaves us with four entries in a two-by-two grid, as in Figure 2.8.

图像

图 2.8.我们的前四个 EDP。

Figure 2.8. Our first four EDPs.

有趣的是,我们从编程理论的基本原则中推导出了这些简单的概念。一旦我们也开始处理对象类型,即使理论仍然简单,概念也会变得更加复杂。让我们看看添加第三个轴时会发生什么。

What’s interesting is that we derived these simple concepts from basic principles of programming theory. Once we start playing with the object types as well, the concepts become much more complex even though the theory remains just as simple. Let’s see what happens when we add that third axis.

图 2.9 显示了三个轴。以前我们根本不关心对象类型,现在我们可以使用之前为对象类型相似性轴定义的四个条目。未知条目在这里的效用与在上一个网格中一样小,因此让我们再次删除它。现在,我们将只关注通过修复方法相似度来定义的右侧切片,如图 2.10 所示。此空间的其余部分将在第 5 章中更全面地讨论。

Figure 2.9 shows the three axes. Where before we were not concerned at all with the object type, we can now work with the four entries we defined earlier for the object type similarity axis. The unknown entry has as little utility here, as it did in the previous grid, so let’s again remove it. For now we’ll concentrate on just the right-hand slice defined by fixing our method similarity to similar, as shown in Figure 2.10. The remainder of this space is more fully discussed in Chapter 5.

图像

图 2.9.设计空间扩展到三个维度。

Figure 2.9. The design space extended to three dimensions.

图像

图 2.10.方法相似性固定为 similar 的设计空间。

Figure 2.10. The design space with method similarity fixed to similar.

我们可以通过认识到对象的类型不能完全不同才能使对象相似并调用类似的方法来将递归放在这个网格上。1 但是,递归的定义是,完全相同的方法实现由自身调用,这确实需要对象和类型的相似性。

We can place Recursion on this grid by recognizing that the type of the object can’t be completely dissimilar for the object to be similar and calling a similar method.1 The definition of Recursion, however, is that the exact same method implementation is being called by itself, which does require both object and type similarity.

同样,我们可以通过意识到将 Redirection 描述为不同的对象和不同的类型是最好的,从而将 Redirection 放在网格上。这是重定向的最通用形式。但是,如果我们进行增量更改,并且保留对象差异,但使对象具有相同的类型,会发生什么情况?这种常见的设计特征,即一个对象将请求移交给另一个相同类型的对象,形成一个类似对象链,在它们之间分配任务。因为它结合了 Redirection (一个不同的对象) 和 Recursion (相同类型) 的各个方面,所以我们使用了 Redirected Recursion 这个非常聪明的名称。阿拉伯数字

Likewise, we can place Redirection on the grid by realizing that it is best descri bed as a dissimilar object and a dissimilar type. This is the most general form of Redirection. What happens when we make an incremental change, though, and we retain object dissimilarity but make the objects of the same type? This common design trait, where an object hands off a request to another object of the same type, forms a chain of like objects parceling out a task among themselves. Because this combines aspects of both Redirection (a dissimilar object) and Recursion (the same type), we use the extremely clever name of Redirected Recursion.2

让我们继续探索 Redirection 并稍微更改一下类型关系。如果我们将方法任务重定向到另一个不是相同类型但当前对象的超类型的对象,我们可以让整个类型系列以多态方式处理调用。你会注意到,这是我们的讨论中第一次明确地出现多态性。到目前为止,我们讨论了相似 (相同) 类型和不同 (不同) 类型之间的调用,但我们尚未完善后一类别。现在我们做到了,突然之间,一个全新的面向对象设计水平出现了。3 这种类型关系我们简称为 Subtype。4

Let’s continue this exploration of Redirection and change the type relationship a bit. If we redirect a method task to another object that is not of the same type, but a supertype of the current object, we can let an entire family of types handle the call polymorphically. You’ll notice that this is the first time polymorphism has explicitly popped up in our discussion. Until now, we talked about calls between similar (same) types and dissimilar (different) types, but we hadn’t refined the latter category. Now we have, and suddenly a whole new level of object-oriented design comes to light.3 This type relationship we simply call Subtype.4

我们可以添加不同类型 bin 的另一个细分,即 Sibling 类型关系。当所涉及的对象的类型共享一个公共祖先超类型,但两个都不是另一个对象的超类型时,就会发生同级关系。我们将方法调用抛弃了我们的类型层次结构,然后将其限制回该树的受信任子集。我们将这种变体称为 Deputized Redirection

We can add one more subdivision of the dissimilar type bin, and that is the Sibling type relationship. A sibling relationship occurs when the types of the objects involved share a common ancestor supertype, but neither is a supertype of the other. We tossed the method call up our type hierarchy and then limited it back down to a trusted subset of that tree. We call this variation Deputized Redirection.

让我们回到 Recursion,然后向右滑动一个方块,其中对象实例相同,但类型关系是一个子类型。再读一遍。实际上,谈论拥有相同的对象以及从该对象到自身的方法调用确实有意义,但该方法调用涉及两个对象类型。事实上,这样做是超级棒的。

Let’s go back to Recursion, and then slide one square to the right, where the object instance is the same, but the type relationship is a subtype. Read that again. It actually does make sense to talk about having the same object, and a method call from that object to itself, but with two object types involved in that method call. In fact, to do so is super.

具体来说,super 允许您从子类型中访问超类型的方法实现。在 Java 中,它是通过关键字完成的。在 C++ 中,使用显式类型作用域机制,如 .其他语言有多种机制,但它们都做同样的事情 — 它们为对象提供对超类型的方法实现的访问权限。这有什么用?好吧,考虑这样一种情况:您想要扩展方法的功能,而不是完全用新功能替换它。您将对类型进行子类化,重写该方法,然后将对原始方法的调用包装在额外的代码中。如您所料,此 EDP 简称为 Extend MethodsuperSupertype::

Specifically, super lets you access a supertype’s implementation of a method from within a subtype. In Java, it is done with the super keyword. In C++, an explicit type-scoping mechanism is used as in Supertype::. Other languages have a variety of mechanisms, but they all do the same thing—they give an object access to a supertype’s implementation of a method. How is this useful? Well, consider a case where you want to extend the functionality of a method, not completely replace it with new functionality. You would subclass the type, override the method, and then wrap a call to the original method in your bit of extra code. As you might expect, this EDP is simply called Extend Method.

请注意,在前面的讨论中,我们采用了一个现有的、熟悉的概念,并在每个阶段只将它移开了一个空间,一次调整一个或另一个细节。这种简单进展的结果是编程中概念和设计元素的广泛集合。如果您不相信我们已经涵盖了很多概念基础,请查看前面 8 个 EDP 的各种 UML 图。只考虑涉及递代理重定向的想法,如图 2.112.12 所示。它们看起来一点也不相似,但是对于单个方法调用,它们在设计空间中彼此相距仅三步。

Notice that in the preceding discussion, we took an existing, familar concept and moved just one space away from it at each stage, tweaking one detail or another at a time. The result of this simple progression is a surprisingly broad collection of concepts and design elements from programming. Look at the variety of the UML diagrams for the preceding eight EDPs if you’re not convinced that we’ve covered a lot of conceptual ground. Consider just the ideas involving Recursion and Deputized Redirection, shown in Figures 2.11 and 2.12. They look nothing alike, yet they sit just three steps from each other in the design space for a single method call.

图像

图 2.11.递归示例 UML。

Figure 2.11. Recursion Example UML.

图像

图 2.12.代理重定向示例 UML。

Figure 2.12. Deputized Redirection example UML.

同样,还剩下两个神秘盒子。这次我们不要忽视它们,而是讨论它们在概念上的含义。在左侧的第一个插槽中,我们有相同(或相似)的对象,但在 method call reliance 的每一侧都有完全不同的类型。这怎么可能呢?嗯,我不确定。尽管存在一些相当深奥的类型关系,但这种关系没有多大意义。目前,这是一个未知的领域,但也许将来会有一种语言为这种上下文组合找到合理的用途。例如,BETA 语言 [26] 有一个独特的内部结构,它是 的镜像,创造了一个 超类型相似性,而不是一个子类型相似性。然而,这种情况非常罕见,因此没有在我们的对象相似性轴中使用。如果它被证明是一个有趣且可行的设计元素,则可以稍后添加它。super

Again, two mystery boxes remain. Let’s not ignore them this time but instead discuss what they could mean conceptually. In the first slot on the left, we have the same (or similar) object but utterly dissimilar typing on each side of the method call reliance. How can this be? Well, I’m not sure. Although some rather esoteric typing relationships exist out there, this one doesn’t make a lot of sense. For now, it is unknown territory, but perhaps a language will find a reasonable use for this context combination in the future. The BETA language [26], for example, has a unique inner construct that is the mirror image of super, creating a Supertype similarity instead of a Subtype similarity. This is exceedingly rare, however, and not used in our object similarity axis for that reason. It may be added later if it proves to be an interesting and viable design element.

最右侧的插槽也出现了类似的情况。同样,我们有相同(或相似)的对象,但现在 Sibling 类型相似,同样,据我所知,没有语言提供这种可能性。这些开放的插槽已经成熟,可以进行解释和思想实验。最重要的是,我们可以预测设计空间中这些未描述条目的一些属性,就像元素周期表中的元素一样。

An analogous situation occurs for the slot on the far right. Again, we have the same (or similar) object, but now a Sibling type similarity, and again, no language that I am aware of offers this possibility. These open slots are ripe for interpretation and thought experiment. Best of all, we can predict some properties for these undescribed entries in the design space, much like elements in a periodic table.

这只是方法调用 EDP 的一半多一点,稍后将在参考部分完整介绍。一个方法调用可以表达多少个概念,这很了不起,不是吗?一个方法调用。这就是我们一直在讨论的全部内容,但我们还有大量的设计组件可供使用。

That’s just over half the method call EDPs that will be presented in full later in the reference section. It’s remarkable how many concepts can be expressed with a single method call, isn’t it? One method call. That’s all we’ve been discussing, and yet we have a wide range of design components to play with.

2.3. 核心 EDP

2.3. Core EDPs

到目前为止,我们只讨论了方法调用,但回想一下,我们说过还有其他三种形式的依赖可供使用:字段使用、状态更改和内聚。那些呢?嗯,他们目前仍在探索、充实和写作中。首先使用方法调用,因为它们是最小、最简单的设计空间。其他的都要大一些。但是,要填写第一组,应该解决一些基本概念,它们从形式和实用的角度构成了面向对象编程的核心。

To this point, we’ve discussed just method calls, but recall that we said there are three other forms of reliances that we can play with: field usage, state changes, and cohesion. What about those? Well, they are still being explored at the moment, fleshed out, and written up. Method calls were approached first because they’re the smallest, simplest design space to work with. The others are quite a bit larger. There are a few basic concepts that should be addressed, however, to fill out this first group, and they form the core of object-oriented programming from both formal and pragmatic points of view.

首先,有整个 “制作对象” 的想法。毕竟,这是面向对象编程的显著特征之一,否则整个范式可能会被命名为其他名称。请参阅此处的创建对象 EDP 以获取指导。在这个有利位置上,面向对象编程已经发展了三十年左右,人们一直反对将其作为一种编程方法,这似乎很奇怪,但它有很大的障碍需要克服。一个反对意见是,任何可以在面向对象编程中实现的东西都可以在过程编程中实现。虽然从技术上讲是正确的,但在任何更高级别的系统中,任何可行的内容都可以在过程、汇编器或原始二进制文件中实现;否则它就不会运行 — 面向对象的编程允许轻松执行某些原则。Create Object 概述了其中一个主要目标,即同时在单个对象和单个类型之间创建关系。

To begin with, there’s the entire “making objects” idea. It is one of the distinguishing characteristics of object-oriented programming, after all, or the entire paradigm probably would have been named something else. Look at the Create Object EDP for guidance here. At this vantage point, three decades or so into object-oriented programming, it seems odd that there was ever objection to it as a programming approach, but it had large hurdles to overcome. One objection was that anything that can be implemented in object-oriented programming can be implemented in procedural programming. While technically true—anything doable in any higher-level system is doable in procedural, in assembler, or in raw binary; otherwise it wouldn’t run—object-oriented programming allowed the easy enforcement of certain principles. Create Object outlines one of the major ones, creating a relationship between a single object and a single type at the same time.

一旦我们创建了对象,我们如何让它们相互通信?嗯,显然是通过使用方法调用。好吧,但是我们首先如何让他们找到彼此呢?我们可以静态地创建对象之间的所有连接,但这相当有限,因为这意味着系统无法对运行时输入做出反应。程序员必须预见系统的所有可能用途,包括诸如满足所有可能的数据需求的最大内存使用等详细信息,而不管实际使用量如何。为了解决这个问题,我们必须允许对象在执行期间访问其他对象。Retrieve EDP 执行此操作,在对象之间动态创建运行时连接。

Once we create objects, how do we get them talking to one another? Well, by using method calls, obviously. Okay, but how do we get them to find one another in the first place? We could statically create all the connections between the objects, but that’s rather limiting because it means the system cannot react to runtime input. The programmer must anticipate all possible uses of the system, including details such as maximal memory use for all possible data needs, regardless of the actual amount used. To get around this problem, we have to allow objects to gain access to other objects during execution. The Retrieve EDP does this, dynamically creating runtime connections between objects.

要实例化一个对象,我们需要一个类型来执行此操作。通常,我们可用的类型几乎是正确的,但并不完全正确。不过,与其每次都从头开始重写合适的类型,不如利用我们已经拥有的资源作为起点。重用类型的功能是通过另一个核心 EDP 继承实现的。继承是一种类型依赖,我们前面通过它的别名之一 subtyping 提到过。它在基类型与依赖它的大部分基本功能(方法)和状态(字段)的类型之间形成连接。它允许新类型以格式正确的方式重用大块的 logic 和数据。

To instantiate an object, we need a type from which to do so. Often the types available to us are almost, but not quite, correct. Instead of rewriting a proper type from scratch every time, though, we’d like to leverage what we already have as a starting point. The ability to reuse types is made possible through Inheritance, another core EDP. Inheritance is a type reliance, which we mentioned earlier by one of its aliases, subtyping. It forms a connection between a base type and a type that relies on it for much of its basic functionality (methods) and state (fields). It allows a new type to reuse large chunks of logic and data in a well-formed way.

另一个与类型相关的 EDP 是 Abstract Interface。它在两种类型之间形成一种依赖,这种依赖有点不同,因为只有关系的一端是提前知道的。它使用一种方法作为两种类型之间的桥梁。如果一个类型声明一个方法为抽象方法,那么它就不必(从最严格的意义上说,也不允许)给出一个方法体实现。这个 EDP 创建了一个 Promise,在某个时候,另一个类型将从这个类型继承,并为该方法提供适当的实现。该类型是什么尚不清楚,但必须发生才能使该类型可用。在此之前,此类型是不完整的。

Another type-related EDP is Abstract Interface. It forms a reliance between two types that is a bit different in that only one end of the relationship is known ahead of time. It uses a method as a bridge between the two types. If a type declares a method to be abstract, it doesn’t have to—and in the strictest sense isn’t allowed to—give a method body implementation. This EDP creates a promise that at some point another type will inherit from this one and give the method a proper implementation. What that type is isn’t yet known, but it has to happen for this type to be usable. Until then, this type is incomplete.

这四个 EDP,Create ObjectRetrieveInheritanceAbstract Interface,允许我们创建具有某些保证的对象,在运行时将它们彼此关联,根据其他类型定义对象类型,并为将来的未指定类型声明 Promise。它们共同构成了面向对象编程的大部分基础。它们与称为 EDP 的方法(如 Delegation 和 Recursion)一起,为将设计视为一门可重复的学科提供了一个坚实的开端,该学科使用格式良好的构建块,以有条不紊和精确的方式连接起来。为什么我们不看看我们能用少数几个人做些什么呢?

These four EDPs, Create Object, Retrieve, Inheritance, and Abstract Interface, allow us to create objects with enforcement of certain guarantees, relate them to one another at runtime, define object types in terms of other types, and declare promises for future, unspecified types. Together, they form much of the basis for object-oriented programming. Along with the method call EDPs such as Delegation and Recursion, they provide a solid start to treating design as a reproducible discipline, one that uses well-formed building blocks connected in methodical and precise ways. Why don’t we see what we can do with just a handful of them?

2.4. 总结

2.4. Conclusion

本章向您介绍了 Elemental Design Patterns。它简要概述了他们帮助解决的驾驶问题以及他们是如何产生的一些背景。您了解了如何用较小的模式来描述 Decorator 等模式。这导致了结论,为了更好地描述标准文献中更抽象的设计模式,更细粒度的模式是必要的。

This chapter introduced you to the Elemental Design Patterns. It gave a brief synopsis of the driving problem they helped solve and some background on how they came about. You saw how patterns such as Decorator can be described in terms of smaller patterns. This led to the conclusion that to better describe the more abstract design patterns in the standard literature, finer-grained patterns are necessary.

您了解了面向对象编程理论的极简形式,并展示了它如何产生编程中可能出现的少量可能的关系或依赖。这些依赖构成了我们可以定义的最小模式的基础。其中一种依赖,即方法调用依赖,构成了本书的核心。您看到,每个方法调用都有一个上下文,该上下文由三个简单的信息(对象、类型和方法相似性)定义,并且这些信息创建了一个设计空间,其中存在众所周知的编程概念。其他三个依赖 — field use、state change 和 cohesion — 产生了它们自己的设计空间,这些空间正在被定义。

You were introduced to a minimalist form of object-oriented programming theory and shown how it gives rise to a small number of possible relationships, or reliances, that can occur in programming. These reliances form the basis for the smallest patterns we can define. One of these reliances, the method-call reliance, forms the core of this book. You saw that every method call has a context, defined by three simple pieces of information—the object, type, and method similarities—and that these create a design space in which well-known programming concepts live. The other three reliances—field use, state change, and cohesion—give rise to their own design spaces that are in the process of being defined.

方法调用设计空间被轻松探索。为许多 EDP 提供了代码示例,您可以看到它们彼此之间的关系。最后,您学习了一组核心 EDP,它们定义了面向对象编程的许多基本概念。

The method-call design space was explored lightly. Code examples were given for many of the EDPs, and you saw how they relate to one another. Finally, you learned a set of the core EDPs, which define many of the basic concepts that underlie object-oriented programming.

您现在有了坚实的基础来了解 EDP 的重要性和实用性,并准备好开始学习如何使用它们。在第 3 章中,您将学习一种以图形方式描述模式实例的宝贵方法,以帮助您可视化它们的交互。

You now have a solid footing to understand the importance and utility of the EDPs and are ready to start learning how to work with them. In Chapter 3, you’ll learn a valuable way to graphically depict pattern instances to help you visualize their interactions.

3. 模式实例表示法

3. Pattern Instance Notation

在我们探讨我们可以对元素设计模式 (EDP) 执行的操作的影响之前,我想先介绍一下新的图形表示法,即模式实例表示法 (PIN)。从这里开始,将使用 PIN 来帮助您直观地了解我们讨论的一些概念。

Before we explore the impact of what we can do with Elemental Design Patterns (EDPs), I want to take a bit of a side jaunt and introduce a new graphical notation, the Pattern Instance Notation, or PIN. PIN is used from here out to help you visualize some of the concepts we discuss.

本章提供了 PIN 的非正式描述以及它在本书中的使用方式。如果您对更多信息或如何将 PIN 用于软件设计的工具支持感兴趣,“The Pattern Instance Notation: A Simple Hierarchical Visual Notation for the Dynamic Visualization and Comprehension of Software Patterns” [36] 中对 PIN 进行了完整描述。

This chapter provides an informal description of PIN and how it is used in this book. If you’re interested in further information or in how PIN can be used in tool support for software design, PIN is fully described in “The Pattern Instance Notation: A Simple Hierarchical Visual Notation for the Dynamic Visualization and Comprehension of Software Patterns” [36].

3.1. 基础

3.1. Basics

PIN 是构成模式的概念和想法的可视化表示形式。它旨在作为一种快速简便的方式来记录和描述设计模式及其交互。之所以选择名称 PIN,是因为它在显示其他图表符号(如 UML)中的模式或概念实例时也很有用。这是最初的用例。

PIN is a visual representation of the concepts and ideas that comprise patterns. It is intended as a quick and easy way to document and describe design patterns and their interactions. The name PIN was chosen because it also is useful in showing instances of patterns or concepts in other diagramming notations, such as UML. This was the original use case.

第 2 章2.2.2 节中,我提到了设计模式规范的 Participants 部分。参与者是设计模式中必须存在的部分,设计模式的实例才能出现。它们的另一个名称是设计模式的角色,因为每个参与者在将模式组合在一起时都扮演着特定的角色。换句话说,每个参与者都扮演着模式的角色。当我们说“Decorator 模式的名为 ConcreteDecorator 的参与者”时,我们真正的意思是“实现的类,它履行 Decorator 模式的 ConcreteDecorator 角色”。这些角色也是抽象的,因为它们为将充当参与者的实现或设计功能提供名称和约束。

In Chapter 2, Section 2.2.2, I mentioned the Participants section of a design pattern specification. Participants are the parts of a design pattern that must exist for an instance of the design pattern to occur. Another name for them are the roles of the design pattern, because each participant has a particular role to play in bringing together the pattern. In other words, each participant fulfills a role of the pattern. When we say “the participant named ConcreteDecorator of the Decorator pattern,” what we really mean is “the class of the implementation that fulfills the ConcreteDecorator role of the Decorator pattern.” The roles are abstractions as well, because they provide a name for, and constraints on, the implementation or design features that will act as participants.

这与《哈姆雷特》等戏剧没有什么不同。哈姆雷特是一个角色,奥菲莉亚、罗森-克兰茨和吉尔登斯滕也是如此。它们描述了一个角色,并为将要扮演每个角色的演员类型提供了指导。演员是舞台制作的参与者,所有演员都合作形成舞台制作。

It’s not unlike a play, such as Hamlet. Hamlet is a role, as are Ophelia, Rosen-crantz, and Guildenstern. They describe a part and provide guidelines for the kind of actor who will be cast in each role. The actor is a participant in the stage production, and all the actors collaborate to form it.

该剧类似于设计模式,而特定的舞台制作是该模式的一个实例。该脚本类似于设计模式规范:它描述角色,并描述它们如何通信和交互。扮演这些角色的演员是参与者。

The play is analogous to a design pattern, and the particular stage production is an instance of the pattern. The script is like the design pattern specification: it describes roles, and it describes how they communicate and interact. The actors who are cast in those roles are the participants.

UML 具有图形功能,即协作元素,用于描述此类情况。使用 Decorator 的示例如图 3.1 所示。这无论如何都不是一个坏符号,而且它非常灵活,但它针对我们的需求存在一些问题。首先,除了在琐碎的图表中,它在实际使用中都有点混乱。它仅向 UML 图添加信息。使用得越多,关系图就越复杂。抽象(如设计模式)旨在通过让我们在更高的抽象级别进行操作来降低复杂性和细节。UML 协作的一个症结在于它们无法降低 UML 图的复杂性。它们无法像更高级别的抽象那样减少公开的信息量。协作元素首先还需要放置一个完整的 UML 图。当我们讨论 UML 功能时,这似乎是一个奇怪的抱怨,但事实证明,在很多情况下,我们可能希望讨论模式如何交互,而不必为实现它们所需的所有部分创建一个图表。

UML has a graphical feature, the collaboration element, to describe such situations. An example using Decorator is shown in Figure 3.1. This isn’t a bad notation by any means, and it is very flexible, but it suffers from a couple of problems for our needs. For one thing, it is a bit messy in practical use at anything other than in trivial diagrams. It only adds information to a UML diagram. The more you use it, the more complex the diagram becomes. Abstractions such as design patterns are intended to reduce complexity and detail by letting us operate at a higher level of abstraction. A sticking point of UML collaborations is that they aren’t capable of reducing the complexity in a UML diagram. They can’t reduce the amount of exposed information as a higher level of abstraction should. A collaboration element also requires a complete UML diagram to be placed in, in the first place. This may seem like an odd complaint when we’re discussing a UML feature, but it turns out that there are plenty of situations where we may want to discuss how patterns interact without having to create a diagram for all the pieces necessary to implement them.

图像

图 3.1.UML 协作图。

Figure 3.1. UML collaboration diagram.

设计模式的另一种常见表示法是 Gang of Four (GoF) 引入的 pattern:role 表示法 [21]。它也是一个在 UML 上使用的注释,它使用一个以模式和角色命名的外部标志。此注释可以根据需要应用于类、包、字段或方法,如图 3.2 所示。它非常灵活和明显,但并非没有问题。

Another common notation for design patterns is the pattern:role notation introduced by the Gang of Four (GoF) [21]. It is also an annotation to be used on top of UML, and it uses an external flag that is named with the pattern and the role. This annotation can be applied to a class, package, field, or method as needed, as shown in Figure 3.2. It’s very flexible and obvious, but it isn’t without issues.

图像

图 3.2.策略作为 pattern:角色标签。

Figure 3.2. Strategy as pattern:role tags in UML.

当只涉及和显示一个模式时,这种方法还不错,但是当您在包含数百甚至数千个块的图表中隐藏了一个模式时会发生什么?比如说,如图 3.3 所示?

This approach isn’t so bad when only one pattern is involved and displayed, but what happens when you have a pattern hidden in a diagram with hundreds or even thousands of pieces? Say, something like in Figure 3.3?

图像

图 3.3.一个不那么庞大的系统的巨大 UML。

Figure 3.3. Huge UML of a not-so-huge system.

图 3.3 是来自实际项目的实际 UML 图。在这个比例尺下它是不可读的,但实际上,它在任何比例尺下都是不可读的。如果要以实际尺寸打印它,使字体为 10 磅大小,则它将是 86 x 32 英尺。那是很多纸张:3,290 张标准美国信纸大小的纸张。在这个大小下,它太小而无法阅读,但在完整大小下,它太大而无法直观地搜索。令人沮丧的是,图表中记录了大量的模式,随时可以被发现和学习,以帮助记录系统。

Figure 3.3 is an actual UML diagram from an actual project. It’s unreadable at this scale, but practically speaking, it’s almost unreadable at any scale. If you were to print it at actual dimensions, such that the type was 10-point size, it would be 86 by 32 feet. That’s a lot of paper: 3,290 standard U.S. letter-size sheets. At this size, it’s too small to be read, but at the full size, it’s too large to be searched visually. Frustratingly, a wealth of patterns are recorded in the diagram, ready to be found and learned from to help document the system.

作为一个更具体的例子,考虑一个 UML 图,如图 3.4 所示。图 3.4 中有两个 Strategy 模式的实例,但是使用 pattern:role 表示法,我们无法判断哪些部分与哪个模式实例对应,即使在这个小例子中也是如此。

As a more concrete example, consider a UML diagram such as in Figure 3.4. There are two instances of the Strategy pattern in Figure 3.4, but using the pattern:role notation, we can’t tell which pieces go with which pattern instance, even in this small example.

图像

图 3.4.在 UML 中作为 pattern:role 标签的多个 Strategy 实例。

Figure 3.4. Multiple instances of Strategy as pattern:role tags in UML.

这就是 PIN 的最初原因:能够将单个图表中的多个模式实例清楚地表示为第一类实体。因此得名 Pattern Instance Notation。其他目标是表示法简单、灵活,并且可以与其他表示法(如 UML)结合使用,而无需依赖它们。

This was the initial reason for the PIN: to be able to clearly express multiple instances of patterns in a single diagram as first-class entities. Hence the name, Pattern Instance Notation. Other goals are that the notation be simple, flexible, and usable in conjunction with other notations such as UML without being reliant on them.

3.2. 密码盒

3.2. The PINbox

PIN 中的核心表示法是 PINbox。它表示单个模式实例,并允许您选择要公开的粒度级别。我们从最简单的 PINbox 开始,然后逐步发展到更有趣的变体。

The core notation in PIN is the PINbox. It represents a single pattern instance and lets you choose the level of granularity you want to expose. We start with a PINbox in its simplest form and work our way up into more interesting variants.

3.2.1. 折叠的 PIN 框

3.2.1. Collapsed PINbox

折叠的 PINbox 只是一个带有粗双边框的名称标签,如图 3.5 所示。内部边界描边是一个矩形,外部边界描边具有弯曲的角。如果可能,边框为灰色阴影。框上的标签是模式的名称。

The collapsed PINbox is just a name label with a thick double border, as in Figure 3.5. The interior border stroke is a rectangle, the external border stroke has curved corners. If possible, the border is shaded gray. The label on the box is the name of the pattern.

图像

图 3.5.折叠的 PINbox。

Figure 3.5. Collapsed PINbox.

就是这样。这是最简单的 PIN 码形式。别担心,还有更多功能即将推出。不过,即使在这里,我们也有一些有用的东西可以使用。我们可以将其用作 UML 或其他图表的注释,只需从 PINbox 中画一个箭头到图表中与它关系最密切的任何元素,如图 3.6 所示。当谈到图形符号时,经验表明方框和线条几乎就是最简单的。

That’s it. That’s the simplest form of a PINbox. Don’t worry, there’s more to come. Even here, though, we have something useful to work with. We can use this as an annotation onto a UML or other diagram by just drawing an arrow from the PINbox to whatever element in the diagram is most closely associated with it, as in Figure 3.6. When it comes to graphical notations, experience shows that boxes and lines are about as simple as it gets.

图像

图 3.6.折叠的 PINbox 作为注释。

Figure 3.6. Collapsed PINbox as annotation.

有关这在现实生活中如何运作的一些示例,请考虑图 3.73.8。使用 SingletonAbstract Factory 的示例显示了与类图一起使用的 PIN,而 Template Method 表示序列图中的模式。

For some examples of how this might work in real life, consider Figures 3.7 and 3.8. The example using Singleton and Abstract Factory shows PIN being used with a class diagram, while Template Method indicates a pattern in a sequence diagram.

图像

图 3.7.类图中的 SingletonAbstract Factory

Figure 3.7. Singleton and Abstract Factory in class diagram.

图像

图 3.8.序列图中的 Template Method

Figure 3.8. Template Method in sequence diagram.

折叠的 PINbox 最适合在您需要助记符时使用,快速提醒系统中存在某种模式,并且该模式具有您可以指向的单个定义功能。这是一种非正式的符号,但具有实用性,尤其是在绘制新设计时。

The collapsed PINbox is best used when you need a mnemonic, a quick reminder that a pattern exists in a system, and the pattern is one that has a single, defining feature you can point to. This is an informal notation but one that has utility, particularly when sketching out a new design.

3.2.2. 标准 PIN 码盒

3.2.2. Standard PINbox

更上一层楼,我们有标准的 PINbox。我们展开折叠表单的灰色粗边框,以便可以向其添加文本。具体来说,我们添加构成中间列出的模式的角色,如图 3.9 所示。这些角色的命名遵循第 2 章 2.2.1 节中概述的相同约定。添加角色可以让我们表达一些更精细的连接。例如,因为每个模式都有一组格式良好且不同的角色,我们可以将每个角色连接到 UML 类或序列图的元素,如图 3.103.11 所示。

Taking things up a level, we have the standard PINbox. We expand the thick gray border of the collapsed form so that we can add text to it. Specifically, we add the roles that make up the pattern listed in the middle, as in Figure 3.9. The roles are named using the same conventions outlined in Chapter 2, Section 2.2.1. Adding the roles lets us express some significantly finer-grained connections. For instance, because each pattern has a well-formed and distinct set of these roles, we can connect each role to the elements of a UML class or sequence diagram, as in Figures 3.10 and 3.11.

图像

图 3.9.标准 PIN 码框。

Figure 3.9. Standard PINbox.

图像

图 3.10.用于 UML 类图的 PIN。

Figure 3.10. PIN used with UML class diagram.

图像

图 3.11.与 UML 序列图一起使用的 PIN。

Figure 3.11. PIN used with UML sequence diagram.

边框周围角色名称的排列由您决定;但是,在最终图表上看起来最好是好的。无论哪种顺序使最终图表最容易阅读,都是首选。

The arrangement of the role names around the border is up to you; however it looks best on the final diagram is fine. Whatever ordering makes the final diagram easiest to read is preferred.

这看起来非常像图 3.1 中的 UML 协作,但 PINbox 可以做一些协作不能做的事情。请注意,在图 3.11 中,Flyweight 的实例具有 AbsFlyweight 角色,未附加到任何对象。在这种情况下,这很好,因为我们只是试图描述其他三个角色之间的交互。如果我们对协作元素进行尝试,并希望保留有关 Flyweight 模式角色的所有信息,那么我们要么必须有一个带有未使用角色名称的悬空箭头,要么更糟糕的是,根本不显示它。两者都不是最佳选择。使用 PINbox,我们不会丢失信息,也不会不必要地弄乱我们的图表。再举一个例子,假设我们在软件设计中有两个模式实例,我们想表明在一个模式实例中实现角色的同一个类在另一个模式实例中实现一个角色,将两个模式实例捆绑到一个组合结构中。我们可以如图 3.12 所示。请注意,此图不使用任何 UML;它仅以特定方式显示两个 pattern 之间的联系。当我们将多个 PIN 盒连接在一起时,事情会变得更加有趣。

This looks an awful lot like a UML collaboration from Figure 3.1, but PINboxes can do some things that collaborations can’t. Note that in Figure 3.11, the instance of Flyweight has the AbsFlyweight role not attached to anything. This is fine in this case because we’re just trying to depict the interactions between the other three roles. If we tried this with a collaboration element and wanted to retain all of the information about the roles of the Flyweight pattern, we’d either have to have a dangling arrow with the unused role name or, worse, not show it at all. Neither is optimal. With the PINbox, we don’t lose the information, and we don’t clutter up our diagram needlessly. For another example, say we have two pattern instances in a software design and we want to indicate that the same class that fulfills a role in one fulfills a role in another, tying the two pattern instances into a combined structure. We could do this as in Figure 3.12. Notice that this diagram doesn’t use any UML; it only shows the connection between two patterns in a particular way. Things get much more interesting when we connect multiple PINboxes together.

图像

图 3.12.标准 PIN 角色连接。

Figure 3.12. Standard PIN role connections.

没有 UML。

There is no UML.

没有代码。

There is no code.

没有类、方法、字段,没有任何东西,只有两个模式、两个概念之间的关系,并且这种关系以特定的方式将它们联系在一起。在下一章中,我们将使用此功能来展示如何将模式组合成新概念。

There are no classes, methods, fields, no anything, just a relationship between two patterns, two concepts, and that relationship ties them together in a particular way. We use this capability to show how patterns can be combined into new concepts in the next chapter.

不过,我们还可以做得更多。

We can do still more, though.

3.2.3. 扩展的 PINbox

3.2.3. Expanded PINbox

如果我们对 PINbox 进行一些小的调整,它会变得更加灵活和强大。让我们从展开中间的框开始,该框当前仅包含模式名称。我们最终得到一个类似于图 3.13 的东西。现在,我们可以使用这个新的空白区域来绘制额外的 PIN 框。我们为什么要这样做?嗯,请看图 3.14。在这里,我们填充了画布。这表明 Pattern 有 5 个角色,可以分解为两个较小的子模式:Subpattern A 和 Subpattern B。此外,它还准确地显示了 external ring 上的角色如何映射到 inner sub patterns 的角色。如果我们有子模式 A 和子模式 B,使得子模式 A 中的角色 3 与子模式 B 中的角色 1 由系统中的同一实体完成,那么我们可以将这两个实例替换为 Pattern 的单个实例。换句话说,我们可以稍微提高抽象的层次。我们不必跟踪两件事,而只需要管理一个模式实例。

If we make a small tweak to the PINbox, it becomes much more flexible and powerful. Let’s start by expanding the box in the center, the one that currently just holds the pattern name. We end up with something that looks like Figure 3.13. Now we can use this new blank area to draw additional PINboxes. Why would we want to do this? Well, look at Figure 3.14. Here we’ve filled in the canvas. What this shows is that Pattern has five roles and can be decomposed into two smaller subpatterns: Subpattern A and Subpattern B. Further, it shows exactly how the roles on the outside ring map to the roles of the inner subpatterns. If we have Subpattern A and Subpattern B, such that Role 3 in Subpattern A is fulfilled by the same entity in a system as Role 1 from Subpattern B, then we could replace those two instances with a single instance of Pattern. In other words, we could raise the level of abstraction a bit. Instead of having to keep track of two things, we only have to manage one pattern instance.

图像

图 3.13.空白扩展的 PIN 实例。

Figure 3.13. Blank expanded PIN instance.

图像

图 3.14.扩展的 PIN 实例。

Figure 3.14. Expanded PIN instance.

另一方面,如果我们有一个 Pattern 的实例,那么很明显我们也有每个子模式的实例。他们可能被埋葬了一点,但我们知道他们必须在那里。

On the other hand, if we had an instance of Pattern, it’s clear that we also have an instance of each of the subpatterns. They may be buried a bit, but we know they have to be there.

扩展的 PINbox 让我们可以按照我们想要的程度来揭示 pattern 的层次结构性质。我们可以将单个模式保留在最高抽象级别,或者我们可以不断扩展它以显示更多细节。此外,如图 3.15 所示,我们不仅限于在此画布上使用 PIN。我们可以集成 UML 或其他一些符号来说明特定点。对于 EDP,它不能分解为更小的子模式,显示 UML 的能力提供了直接定义。或者,我们可能希望将演示 Decorator 模式的一种实现的 UML 图放在 PINbox 内。这可以用作学生的参考插图或设计支持工具中的快速提醒。灵活性使 PIN 在描述模式的定义和组合方面如此独特。

The expanded PINbox lets us reveal the hierarchical nature of patterns to whatever degree we wish. We can leave a single pattern at the highest level of abstraction, or we can keep expanding it to show more detail. Also, as shown in Figure 3.15, we are not limited to using just the PIN on this canvas. We can integrate UML or some other notation to illustrate a particular point. In the case of EDPs, which cannot be decomposed into smaller subpatterns, the ability to display UML offers a direct definition instead. Alternatively, we may want to place the UML diagram that demonstrates one implementation of, say, the Decorator pattern, inside the PINbox. This could be used as a reference illustration for a student or as a quick reminder in a design support tool. Flexibility is what makes PIN so uniquely useful for depicting the definition and composition of patterns.

图像

图 3.15.使用 UML 的扩展 PIN 实例。

Figure 3.15. Expanded PIN instance using UML.

3.2.4. 堆叠 PINbox 和多重性

3.2.4. Stacked PINboxes and Multiplicity

许多模式的元素具有多重性。例如,通常,在 Decorator 模式中,有多个参与者履行 Concrete Decorator 角色,如果只有一种工厂,则 Abstract Factory 将失去很多有用性。使用对 PINbox 定义的严格解释,我们必须为元素的每个组合使用一个 PINbox。请看图 3.16 中的基本抽象工厂图。要使用标准 PIN 码框正确注释它,我们需要四个:每个 Concrete Factory 一个乘以每个 Concrete Product。我们现在只讨论两家工厂和两种产品。想象一下,如果有 4 家工厂和 10 种产品。40 个 PIN 码框将无法导航并且非常混乱。我们会这样做,但我们也想确保查看该图的人知道,这些单独的模式实例只是更高级别上同一模式实例的一部分,它们从根本上是相互关联的。我们可以使用堆叠的 PINbox 来显示关系,如图 3.17 所示。

Many patterns have a multiplicity to their elements. Generally, more than one participant fulfills the Concrete Decorator role in a Decorator pattern, for instance, and Abstract Factory loses much of its usefulness if there is only one kind of factory. Using a strict interpretation of the PINbox definition, we’d have to use one PINbox for each combination of the elements. Look at the basic Abstract Factory diagram in Figure 3.16. To properly annotate it with standard PINboxes, we’d need four: one for each Concrete Factory times each Concrete Product. And we’re only discussing two factories and two products right now. Imagine if there were, say, four factories and 10 products. Forty PINboxes would be impossible to navigate and horrendously confusing. And we would but we also want to make sure that someone looking at the diagram knows that these individual pattern instances are simply parts of the same pattern instance at a higher level, that they are fundamentally connected. We can show the relationships by using a stacked PINbox, as in Figure 3.17.

图像

图 3.16.需要多个相关的 PIN 码框。

Figure 3.16. A need for multiple related PINboxes.

图像

图 3.17.堆叠 PINbox。

Figure 3.17. Stacked PINbox.

我们在 PINbox 的“后面”添加了第二个轮廓,以使 PINbox 看起来像一副纸牌一样堆叠。另请注意,我们更改了来自 Concrete Factory、Concrete Product 和 Abstract Product 角色的箭头。尾部现在具有分叉外观,以指示存在多个连接,然后线条拆分以指向以前由唯一 PINbox 指示的每个实体。为了帮助说明拆分发生的位置,在交汇点处添加了一个小圆圈。与以前一样,缺少此指示符的交界点是同一元素满足多个角色的位置。

We added a second outline “behind” the PINbox to give the appearance of PINboxes stacked like a deck of cards. Also, notice that we changed the arrows coming from the Concrete Factory, Concrete Product, and Abstract Product roles. The tail now has a forked appearance to indicate that there are multiple connections, and then the line splits to point to each of the entities that previously would have been indicated by a unique PINbox. To help illustrate where the splits occur, a small circle is added at the junction points. Junction points that lack this indicator are where multiple roles are satisfied by the same element, as before.

通常,应谨慎使用 multiplicity connections 和 PINbox。请记住,我们的目标是简单性,如果两个角色具有与之关联的多重性,我们必须非常清楚,这种关系是由我们所表示的抽象正确定义的。在这种情况下,具有三个具有多重性的角色是有意义的。每个 Concrete Factory 都必须处理每个 Concrete 产品,并且 Abstract Products 与它们相应的 concrete 子类明确相关。在这种情况下,很难混淆。

In general, multiplicity connections should be used with care with PINboxes. Remember our goal is simplicity, and if two roles have multiplicities associated with them, we have to be extremely clear that the relationship is properly defined by the abstraction we are denoting. In this case, having three roles with multiplicity makes sense. Each Concrete Factory must handle each of the Concrete Products, and the Abstract Products are clearly linked with their corresponding concrete subclasses. It’s hard to get confused in this case.

但是,如果有多个 Abstract Factory 实体,则每个实体都需要创建一个新的堆叠 PINbox 实例。可以这样想:如果一个模式很适合用作折叠的 PINbox,那么当且仅当使其在折叠形式中有用的角色没有在重数中关联时,它很可能作为具有多个多重性的堆叠 PINbox 有用。换句话说,如果有一个角色非常突出,以至于它可以在折叠形式中成功使用,那么它可以用作堆叠 PINbox 的关键角色。

If, however, there were multiple Abstract Factory entities, each would necessitate the creation of a new stacked PINbox instance. Think of it this way: if a pattern lends itself well to use as a collapsed PINbox, then it likely will be useful as a stacked PINbox with several multiplicities, if and only if the role that makes it useful in the collapsed form is not associated in a multiplicity. In other words, if there is one role that is so prominent that it can be used in the collapsed form successfully, then it can be used as the linchpin role for a stacked PINbox.

在此基础上,图 3.18 重新审视了图 3.4 中的情况,其中我们有多个 Strategy 实例,但无法可靠地区分它们。现在我们有两个 Strategy 集群,每个集群都是不同的,并且清晰可辨。我们使用 stacked 形式来表示每个 pattern 实例有多个具体策略。这当然是有用的,但如果我们想捕捉到我们正在协同使用多个 Strategy 集群的想法呢?——不是单个 Strategy,而是多个 Strategy,共同行动。换句话说,如果我们决定这种多重策略的情况足够普遍,以至于我们想清晰、清晰地告诉其他人呢?在这种情况下,我们可能会用图 3.19 来解释它。我们剥离了 UML,即与我们的实现最接近的部分,并将 Strategy 集群之间的连接抽象成一些简单而精确的东西。这张图比图 3.4 中的原始图要简单得多,它提供了一种机制,可以更快地共享和更清晰地传达我们试图传达的内容:“这里有不止一个策略在起作用,相同的类充当每个策略的具体策略。这甚至可能是新设计模式的种子。

Building on this, Figure 3.18 revisits the situation from Figure 3.4, where we had multiple instances of Strategy but couldn’t reliably distinguish between them. Now we have two Strategy clusters, and each is distinct and clearly discernable. We use the stacked form to indicate that there are multiple concrete strategies for each pattern instance. This is useful, certainly, but what if we wanted to capture the very idea that we are using multiple clusters of Strategy in concert?—not a single Strategy, but more than one, acting together. In other words, what if we decided that this multiple Strategy situation was common enough that we wanted to tell someone else about it, clearly and cleanly. In such a case, we might use Figure 3.19 to explain it. We stripped out the UML, the portion that corresponds most closely to our implementation, and we abstracted out the connection between the Strategy clusters into something simple and precise. This diagram is much simpler than our original in Figure 3.4 and provides a mechanism for faster sharing and clearer communication of what we are trying to get across: “There is more than one Strategy at work here, and the same classes are acting as concrete strategies for each.” This could even be the seed of a new design pattern writeup.

图像

图 3.18.多个 Strategy 实例作为 PIN 框。

Figure 3.18. Multiple Strategy instances as PINboxes.

图像

图 3.19.显示多个策略 PIN 框之间的交互。

Figure 3.19. Showing the interaction between multiple Strategy PINboxes.

3.2.5. 剥离和凝聚

3.2.5. Peeling and Coalescing

扩展 PINbox 的最后一个技巧很有用,尽管我们在本书中没有广泛使用它。回想一下,在讨论 UML 协作表示法时,我提到它只能添加而不是减去图表中的信息,并且由于该限制,它无法作为大规模的抽象机制使用。

There’s one last trick of the expanded PINbox that is useful to know about, even though we don’t use it extensively in this book. Recall that in the discussion of the UML collaboration notation, I mentioned that it can only add, not subtract, information in a diagram and that it fails to be useful as an abstraction mechanism at large scales because of that limitation.

想象一下,我们正在查看一个如图 3.17 所示的系统。我们已经准确地确定了 Abstract Factory 设计模式的多个实例的堆叠 PINbox,但我们并没有使图表变得更简单。这仅是中等有用的。

Imagine that we’re looking at a system such as in Figure 3.17. We’ve accurately identified the stacked PINbox for the multiple instances of the Abstract Factory design pattern, but we haven’t made the diagram any simpler. This is only moderately useful.

想象一下,图 3.17 中的 UML 片段是一个更大的图表的一部分。让我们向它添加一些连接,但不要让它们模棱两可,如图 3.20 所示。很难看出添加了什么,不是吗?现在,让我们将图 3.17 中的 UML 部分放在 PINbox 的扩展形式,如图 3.21 所示。相同的信息仍然存在,但我们现在使用 PINbox 作为代理机制。附加到角色外边缘的任何内容都连接到附加到角色内边缘的内容。您可以将灰色角色边框视为直通层。

Imagine that the UML snippet in Figure 3.17 is instead part of a much larger diagram. Let’s add some connections to it but leave them ambiguous, as in Figure 3.20. It’s difficult to see what was added, isn’t it? Now, let’s place the UML portion from Figure 3.17 inside an expanded form of the PINbox, as in Figure 3.21. The same information is still in place, but we’re now using the PINbox as a proxy mechanism. Anything attached to the outside edge of a role connects to what is attached to the inside edge of a role. You can think of the gray role border as a pass-through layer.

图像

图 3.20.Abstract Factory 作为更大的 UML 图的一部分。

Figure 3.20. Abstract Factory as part of a larger UML diagram.

图像

图 3.21.抽象 Factory 包含在扩展的 PIN 框中。

Figure 3.21. Abstract Factory subsumed within the expanded PINbox.

现在将 PINbox 折叠为标准格式,如图 3.22 所示。图表变得更简单了,不是吗?现在,您可以准确地看到新的连接是什么。设计模式实例的所有细节都已替换为单个 PINbox。PINbox 现在充当了抽象应该的作用,它包含了抽象所代表的细节,并使情况更容易理解,而不是更难理解。请参阅第 7 章第 7.1.1 节中对 Abstract Factory 的讨论,了解如何使用扩展的 PINbox 来显示模式的内部结构。

Now collapse the PINbox to the standard form, as in Figure 3.22. The diagram just got a lot simpler, didn’t it? Now you can see precisely what the new connections are. All the details of the design pattern instance have been replaced by a single PINbox. The PINbox now acts as an abstraction should, by subsuming the details that the abstraction represents and by making the situation easier to comprehend, not harder. See the discussion of Abstract Factory in Chapter 7, Section 7.1.1, for a use of an expanded PINbox to show the internal structure of the pattern.

图像

图 3.22.合并的 PINbox。

Figure 3.22. Coalesced PINbox.

在这种情况下,如果您想显示详细信息,您可以再次展开 PINbox。完全移除 PINbox 并将内部组件重新连接到外部组件的行为称为剥离。本章开头提到的期刊文章 [36] 扩展了这些想法。

In this case, if you wanted to reveal the details, you could expand the PINbox again. The act of removing the PINbox entirely and reconnecting the internals to the externals is called peeling. The journal article [36] mentioned at the beginning of this chapter expands on these ideas.

3.3. 总结

3.3. Conclusion

PIN 旨在成为一种简单而灵活的方式,用于直观地描述软件中的设计元素。它可以通过注释其他软件设计符号(如 UML)来使用,也可以作为独立的符号来显示设计元素(如设计模式)之间的关系,而与实现需求无关。它根据特定需求,为用户提供了几乎无限的粒度选择,即在特定时间点可以公开的内容。

PIN is designed to be a simple and flexible way of visually describing design elements in software. It can be used by annotating other software design notations such as UML or as a standalone notation for showing the relationships between design elements such as design patterns independent of implementation needs. It offers the user nearly unlimited choice in degree of granularity of what can be exposed at a particular point in time, based on specific needs.

4. 使用 EDP

4. Working with EDPs

到目前为止,我们已经使用许多隐喻来讨论元素设计模式 (EDP),包括构建块,以及与化学元素周期表的比较。之所以选择这些隐喻和比较,是因为它们被用于将基本部分组合成更大、更有用的结构至关重要的领域。这也适用于我们,并导致一个假设,即 EDP 可以连接成有意义的更大部分。在第 3 章中,您介绍了模式实例表示法 (PIN),该讨论的一部分展示了如何在图中连接 PINbox,说明了设计模式(如 EDP)的多个实例如何交互。

Until now we’ve talked about Elemental Design Patterns (EDPs) using a number of metaphors, including building blocks, and comparisons to the periodic table of chemistry. Those metaphors and comparisons were selected because they are used in domains where composition of fundamental pieces into larger, more useful constructs is critical. This applies for us as well and leads to an assumption that EDPs can be joined into meaningful larger pieces. In Chapter 3, you were introduced to Pattern Instance Notation (PIN), and part of that discussion showed how PINboxes can be joined in a diagram, illustrating how multiple instances of design patterns such as EDPs can interact.

在本章中,我们将所有这些信息放在一起,并证明 EDP 不仅仅是小的单个概念。EDP 构成了更大型抽象和设计模式的基础,并且可以在多种情况下有效使用,包括设计、实现和重写系统。

In this chapter, we put all of this information together and demonstrate that EDPs are not just small individual concepts. EDPs form the basis for much larger abstractions and design patterns and can be used effectively in a multitude of situations, including designing, implementing, and rewriting a system.

4.1. 形态的组合

4.1. Composition of Patterns

让我们回顾一下第 2 章第 2.2.1 节中对 Decorator 的讨论和解构。我们发现正在使用对象递归,并且 Objectifier对象递归的一部分。解构过程需要对现有的设计模式文献有深入的了解,但最终结果很模糊,并没有让我们对 Decorator 有一个全面的了解。我们将使用 EDP 来提供更完整的画面。

Let’s revisit our discussion and deconstruction of Decorator in Chapter 2, Section 2.2.1. We identified that Object Recursion was being used and that Objectifier was a part of Object Recursion. The deconstruction process required deep knowledge of the existing design patterns literature, yet the end result was vague and did not give us a comprehensive understanding of Decorator. We’ll use EDPs to give a more complete picture.

让我们从刚才描述的一个基本的面向对象编程 EDP 开始,即 Abstract Interface。我们声明过,它承诺未来的子类将为指定方法提供实现。让我们继续用 UML 模拟这个模式的各个部分。我们知道我们有 Abstract Interface,我们知道我们需要 Inheritance,因为我们谈论了一个子类。我们可以如图 4.1 所示。

Let’s start with one of the basic object-oriented programming EDPs we just described, Abstract Interface. We stated that it promises that a future subclass will provide an implementation for the specified method. Let’s go ahead and mock up the pieces of this pattern with UML. We know we have Abstract Interface, and we know we need Inheritance because we talk about a subclass. We can show these as in Figure 4.1.

图像

图 4.1.将接口继承 EDP 抽象为 UML。

Figure 4.1. Abstract Interface and Inheritance EDPs as UML.

现在让我们以非常具体的方式连接这两个 EDP。我们知道 Abstract Interface 谈到了类的“未指定子类”,这表明履行 Abstractor 角色的类是一个超类。让我们通过在 Inheritance 中让 Superclass 角色也指向 Abstractor 类来证明这一点。这会将两个 UML 图合并为一个,如图 4.2 所示。我们应用了在第 3 章 3.2.2 节中首先看到的内容,连接了两个 EDP 实例,并创建了一个更大的设计。我们在此图中添加了另一条信息: Inheritance 的 Subclass 角色提供了 Abstract Interface 中 operator 角色的具体定义。这是新的,未出现在两个 EDP 中。Abstractor

Now let’s connect these two EDPs in a very specific way. We know that Abstract Interface talks about an “unspecified subclass” of the Abstractor class, suggesting that the class fulfilling the Abstractor role is a superclass. Let’s show that by having the Superclass role in Inheritance point also to the Abstractor class. This merges the two UML diagrams into one, as in Figure 4.2. We applied what we first saw in Chapter 3, Section 3.2.2, connecting the two EDP instances, and created a larger design. We added one more piece of information to this diagram: the Subclass role of Inheritance provides the concrete definition of the operator role from Abstract Interface. This is new and appears in neither EDP.

图像

图 4.2.Fulfill Method 的内部定义为 UML。

Figure 4.2. Internal definition of Fulfill Method as UML.

这个新定义满足了作为抽象接口概念本质的契约,即子类将为抽象方法提供实现,并产生了这个新设计模式的名称:Fulfill Method。这是您在本书中尚未看到的概念,但我相信如果您曾经使用面向对象语言进行编程,那么您已经见过它。我们在 3.2.2 节的末尾提到了 pattern 的组成,并在图 3.12 中展示了这一点,但它并没有得到充实。我们现在就开始吧。

This new definition fulfills the contract that is the conceptual essence of Abstract Interface, that a subclass would provide an implementation for the abstract method, and gives rise to the name for this new design pattern: Fulfill Method. This is a concept you haven’t seen yet in this book, although I’m sure you’ve seen it if you’ve ever programmed in an object-oriented language. We alluded to the composition of patterns at the end of Section 3.2.2, and showed this in Figure 3.12, but it wasn’t fleshed out. Let’s do that now.

让我们重绘图 4.2,但没有 UML,正如我们在 PIN 讨论中谈到的那样。在图 4.3 中,删除了实现细节,图中仅显示了两个 EDP 之间的关系。它清楚地抽象出每个 EDP 中包含的关系。留下一个简单的图表,更清楚地显示了这两个概念之间的联系。然后,我们可以用 Fulfill Method PINbox 包装这个新的简化图,如图 4.4 所示。

Let’s redraw Figure 4.2 but without the UML, as we talked about in our PIN discussion. In Figure 4.3, the implementation details are removed, and the figure shows only the relationship between the two EDPs. It cleanly abstracts out the relationships that are contained within each EDP. A simple diagram remains, showing more clearly what the connection is between these two concepts. We can then wrap this new simplified diagram with the Fulfill Method PINbox, as in Figure 4.4.

图像

图 4.3.将 Method 实现为简单的连接 PIN 码框。

Figure 4.3. Fulfill Method as simple connected PINboxes.

图像

图 4.4.Fulfill Method 作为展开的 PINbox。

Figure 4.4. Fulfill Method as expanded PINbox.

这种表示法的真正回报如图 4.5 所示,当我们折叠扩展的 PINbox 并移动到更高的抽象级别时。这等效于前面的每个图,但只是一个简单的 PINbox,可用于注释 UML 图,或者,正如我们稍后将看到的,作为更大的设计模式定义的一部分。PINbox 和设计模式的这种几乎分形的性质在使用 patterns 时会一遍又一遍地出现。设计模式由较小的模式组成,其中最小的不可分割模式由 EDP 描述。反过来,所有设计模式(从 EDP 开始)都可用作较大模式的构建块。在每个粒度级别上,PINbox 都可用于简明扼要地描述正在考虑的概念。

The real payoff for this style of notation is illustrated in Figure 4.5, when we collapse the expanded PINbox and move to a higher level of abstraction. This is equivalent to each of the preceding figures but is a simple, single PINbox that can be used to annotate a UML diagram or, as we’ll see later, as a part of yet larger design pattern definitions. This almost fractal nature of PINboxes and design patterns will come up over and over again in working with patterns. Design patterns are made of smaller patterns, with the smallest, indivisible patterns described by the EDPs. In turn, all design patterns, starting with the EDPs, are usable as building blocks for larger patterns. At every level of granularity, PINboxes can be used to concisely depict the concepts under consideration.

图像

图 4.5.Fulfill Method 作为标准 PINbox。

Figure 4.5. Fulfill Method as standard PINbox.

如果您密切关注,您可能已经注意到,在此过程中我们丢失了一条信息。在最初描述图 4.2 时,我说“我们向此图添加了一条信息: Inheritance 的 Subclass 角色提供了 Abstract Interface 中 operator 角色的具体定义。这是新的,没有出现在两个 EDP 中。然而,该操作的具体定义 — Fulfill Method 的整个定义特征 — 并没有从图 4.2 延续到图 4.3

If you were following closely, you may have noticed that along the way we dropped one piece of information. When originally describing Figure 4.2, I stated that “we add one more piece of information to this diagram: the Subclass role of Inheritance provides the concrete definition of the operator role from Abstract Interface. This is new and appears in neither EDP.” The concrete definition of the operation, however—the entire defining feature of Fulfill Method—was not carried forward from Figure 4.2 to Figure 4.3.

这突出了在研究模式和使用 PIN 时必须牢记的两个要点。首先,PIN 不是模式的正式描述的表示法。它不是为了定义模式的每一个微小特征而设计的。它旨在让您快速轻松地使用模式实例,并在可能的情况下协助描述模式定义。如果你在寻找设计模式的数学上精确的形式化,我只能再次指出附录作为起点。PIN 是这种形式主义的近似形式,旨在供人类消费。

This highlights two important points that you must keep in mind when studying patterns and using PIN. First, PIN is not a notation for the formal description of patterns. It is not designed to define every tiny feature of a pattern. It is designed to let you work with pattern instances quickly and easily and, where possible, assist in the depiction of pattern definitions. If you are looking for a mathematically precise formalization of design patterns, I can only point you once again to the appendix as a starting point. PIN is an approximation of that formalism, intended for human consumption.

其次,更重要的是,你永远不能忘记,当涉及到任何设计模式时,它是书面说明或模式规范 — 设计模式的散文描述 — 是模式的本质和核心。数学符号可以描述模式是什么样子的,它们可以列出和命名各个部分以及它们是如何连接在一起的,但它们永远无法告诉你关于模式最关键的事情。他们无法告诉你为什么。他们无法告诉你何时。充其量,他们能告诉你什么什么是好的,但什么是不智慧的。智慧,即模式旨在传授的知识,是围绕着什么的其他一切。如果您在任何时候对某个模式、如何应用它、它的适用性或它的关键概念感到困惑,请查阅该模式的规范文档。请参阅规格。在你对模式的学习和应用中,诸如 PIN 甚至正式演算之类的符号永远不会完全替代写作,而是一种助记符的帮助。

Second, and much more important, you must never forget that when it comes to any design pattern, it is the write-up or pattern specification—the prose description of a design pattern—that is the essence and heart of the pattern. Mathematical notations can describe what a pattern looks like, they can list and name the pieces and how they are hooked together, but they can never tell you the most critical thing about patterns. They cannot tell you the why. They cannot tell you the when or where. At best, they can tell you the what. What is good, but what is not wisdom. Wisdom, the knowledge that patterns are intended to impart, is everything else surrounding the what. If at any time you are confused about a pattern, about how it is being applied, about its applicability or its critical concepts, consult the canonical document for that pattern. Refer to the specification. In your studies and application of patterns, notations such as PIN or even formal calculi will never be a complete substitute for the write-up, only a mnemonic assistance.

前面的示例可能看起来不多,但它是了解 EDP 以及它们如何形成更大模式的关键步骤。我们采用了两个 EDP,每个 EDP 定义了两个实体之间的一种关系,并将它们拼接在一起以形成一个稍大的模式。你刚才看到的是优秀设计的基本行动——将适合手头问题的小块、易于理解的部分以有意义的方式将它们组合在一起。更重要的是,这个过程没有尽头。每次我们向程序添加新的关系时,我们都会以某种方式改变设计。诀窍是识别哪些改变是有帮助的,哪些改变会导致麻烦。

The preceding example may not seem like much, but it’s a critical step in understanding EDPs and how they form larger patterns. We took two EDPs, each of which define one relationship between two entities, and stitched them together to form a slightly larger pattern. What you just saw is the essential action of good design—taking small, understandable pieces that are appropriate to the problem at hand and putting them together in meaningful ways. What’s more, there is no end to this process. Every time we add a new relationship to a program, we alter the design in some way. The trick is recognizing which alterations are helpful and which ones lead to trouble.

在构造 Fulfill Method 这样一个小的情况下,这似乎是显而易见的。“嗯,当然,”你可能会说,“否则你怎么把这两个 EDP 放在一起呢?好吧,我们可以尝试像图 4.6 一样将它们连接起来。这是相同的两个 EDP,但我们颠倒了哪个类是子类。现在我们不能像我们想要的那样添加该方法定义!1 为了更容易看到,让我们只用 PIN 框重新绘制这个连接,如图 4.7 所示。这与图 4.3 根本不是同一张图。

In such a small case as constructing Fulfill Method, this may seem painfully obvious. “Well of course,” you might say, “how else would you put those two EDPs together?” Well, we could try to connect them as in Figure 4.6. These are the same two EDPs, but we reversed which class is the subclass. Now we can’t add that method definition like we wanted to!1 To make this easier to see, let’s redraw this connection with just the PINboxes, as in Figure 4.7. This is simply not the same graph as Figure 4.3.

图像

图 4.6.在 Fulfill Method 中翻转我们的 EDP — 哎呀。

Figure 4.6. Flipping our EDPs in Fulfill Method—oops.

图像

图 4.7.将 EDP 翻转为 PIN 框。

Figure 4.7. Flipped EDPs as PINboxes.

只有一种特定的概念组合才能让我们达到我们希望达到的目标。这就是设计模式背后的魔力:通过反复试验,我们可以找到解决我们不断遇到的问题的最佳实践解决方案。我们可以将这些解决方案写下来,作为可以重复使用和塑造成新形式的较小概念的组合,同时保留原始概念联系的智慧。EDP 允许我们从主要模块构建最佳实践解决方案,而不是通过反复试验。

Only one specific combination of concepts will get us to where we wish to be. This is the magic behind design patterns: through trial and error, we can find the best-practice solutions to problems we keep running into. We can write down these solutions as a combination of smaller concepts that can be reused and sculpted into new forms while retaining the wisdom of the original conceptual connections. EDPs allow us to build our best-practice solutions out of primary blocks instead of through trial and error.

另一点需要说明的是,图 4.2 显示了满足 PIN 框中的 EDP 的绝对最小 UML,而不是确切的解决方案。这不是实现;这是一个实现。回想一下,我们给自己提供了一种讨论设计问题(如模式)的方法,而不是将自己局限于高度具体和严格定义的实现。我们可以返回到组成 Fulfill Method 的各个 EDP,看看它们在实现上如何有所不同,但仍然是我们希望它们成为的 EDP。

Another point to be made is that Figure 4.2 shows the absolute minimum UML that satisfies the EDPs in the PINboxes, not the exact solution. This is not the implementation; this is one implementation. Recall that we’re giving ourselves a way of talking about design issues such as patterns without limiting ourselves to a highly specific and rigidly defined implementation. We can return to the individual EDPs that comprise Fulfill Method and look at how they can differ in their implementations yet still be the EDPs we wish them to be.

4.1.1. 同位素

4.1.1. Isotopes

任何给定的设计模式,无论是小到 EDP 还是大到成熟的模式(如 Decorator),都可以以多种方式实现,并且仍然体现所描述的概念。这种灵活性是设计模式的标志性优势。我们将这些不同的实现称为 pattern 的同位素。该名称旨在与术语 elemental 一致。在化学中,同位素是特定元素原子的变体。原子有三个组成部分:电子、质子和中子。质子和中子形成原子核,电子围绕原子核运行。原子核中的质子数量决定了原子是什么元素,而电子决定了该原子如何连接并与其他原子相互作用。元素的同位素与该元素的其他原子具有相同的质子数,并且具有相同的电子壳结构。由于这两个特性,它在化学性质上与该元素的其他同位素相同。它以相同的方式连接到其他原子,形成相同的键,并且在大多数情况下,可以认为与该元素的任何其他同位素原子相同。

Any given design pattern, whether one as small as an EDP or as large as a full-blown pattern such as Decorator, can be implemented in a vast number of ways and still embody the concept being described. This flexibility is a hallmark strength of design patterns. We call these differing implementations isotopes of the pattern. The name is intended to be congruent with the term elemental. In chemistry, an isotope is a variation of an atom of a particular element. Atoms have three components: electrons, protons, and neutrons. The protons and neutrons form the nucleus of the atom, and the electrons orbit the nucleus. The number of protons in the nucleus determine what element the atom is, and the electrons determine how that atom connects and interacts with other atoms. An isotope of an element has the same proton count as other atoms of that element, and it has the same electron shell structure. Because of these two traits, it behaves chemically the same as other isotopes of that element. It connects to other atoms in the same way, forms the same bonds, and for most intents and purposes can be considered the same as any other isotopic atom of that element.

然而,在内部,同位素的原子在原子核中具有不同数量的中子。从外面看,它的化学性质和作用是一样的,但内部差异会导致微小的、微不足道的副作用,比如,哦,核裂变。这往往是相当具有破坏性的。

Internally, however, an atom of an isotope has a different number of neutrons in the nucleus. From the outside, it looks and acts the same chemically, but the internal differences can cause small, insignificant side effects, like, oh, nuclear fission. This tends to be rather disruptive.

在软件中,我们将这种意外的核裂变称为崩溃。

In software, we call this unexpected nuclear fission a crash.

软件中 design pattern 的同位素定义类似。它是设计模式的实现,其中模式的核心概念(定义该设计模式与所有其他设计模式不同的内容)和外部接口保持不变。

An isotope of a design pattern in software is defined similarly. It is an implementation of a design pattern in which the core concepts of the pattern—what define that design pattern to be different from all other design patterns—and the external interface remain the same.

换句话说,必须满足的角色与预期的设计模式描述没有区别。2 这类似于原子隐喻中的电子壳层,并在 PINbox 表示法中明确表示。外部实体(如另一个模式实例或代码元素)将看到的只是围绕定义的 “nucleus” 的角色。

In other words, the roles that must be fulfilled do not differ from the expected design pattern description.2 This is analogous to the electron shells in the atom metaphor and is made explicit in the PINbox notation. All that an external entity, such as another pattern instance or code element, will see are the roles surrounding the “nucleus” of the definition.

原子中的质子数是它与所有其他元素的区别,并将其置于元素周期表中的特定位置。我们在设计模式方面也有类似的情况,其中问题、解决方案和上下文是唯一的。设计模式做什么、解决什么等等,就是它的鉴别器。这就是它与所有其他设计模式的不同之处。改变问题,我们就会有不同的设计模式。改变上下文,设计模式必须再次适应并更改为其他内容。如果这是一个小的变化,并且生成的新模式看起来与原始模式非常相似,那么我们有一个变体;如果这是一个很大的变化,那么我们可能会有全新的东西。

The proton count in an atom is what distinguishes it from all other elements and places it in a specific position in the periodic table. We have a similar situation with design patterns, where the problem, solution, and context are unique. What the design pattern does, what it solves, and so on, is its discriminator. It is what makes it unique from all other design patterns. Change the problem, and we have a different design pattern. Change the context and, again, the design pattern must adapt and change into something else. If it’s a small change, and the resulting new pattern looks a lot like the original, then we have a variant; if it’s a large change, then we may have something wholly new.

对于 EDP,与原子和元素周期表的类比更加强烈,因为 Section 2.2.4 中定义的设计上下文轴创建了一个定义明确的空间,我们可以在其中唯一地放置每个 EDP。这与化学的元素周期表直接相媲美,后者创建了一个定义明确的空间,可以在其中放置元素并预测这些元素的性质。

With EDPs, the analogy to atoms and the periodic table is even stronger, because the axes of design context defined in Section 2.2.4 create a well-defined space within which we can uniquely place each EDP. This is directly comparable to chemistry’s periodic table, which creates a well-defined space in which the elements can be placed and the properties of those elements can be predicted.

EDP 在方法依赖型 EDP 设计空间中的位置(由第 2.2.4 节中的对象、类型和方法相似性轴定义)是绝对的。更改这三个轴中任何一个轴的值,该空间内的位置都会发生变化。原始 EDP 已突变为另一个 EDP,您可以预测新 EDP 的特性。但是,更改有关 EDP 的任何其他内容,例如它在特定实例中的表示方式,EDP 将在实现发生变化时保持不变 - 不同的实现但相同的属性。

The position of an EDP within the method-reliance EDP design space—as defined by the object-, type-, and method-similarity axes in Section 2.2.4—is absolute. Alter the value of any one of these three axes and the position within that space changes. The original EDP has mutated to another one, and you can predict what the properties of the new EDP will be. Change anything else about the EDP, however, such as how it is expressed in a particular instance, and the EDP stays the same while the implementation shifts—different implementation but the same properties.

对于同位素,无论是化学的还是设计模式的,不同的是内部结构。在原子中,变化的是中子数。在设计模式的实例中,它是该实例的实现方式。这些可能会产生广泛的影响,即使它们无法从外部立即识别出来。在任何时候,一个不稳定的原子都可能根据它的中子数分裂,并且在任何时候,一个设计模式的实现不佳的实例都可能导致崩溃,即使从外面看起来不错。

With an isotope, either chemical or in design patterns, what differs is the internal structure. In the atom, it is the neutron count that changes. In the instance of a design pattern, it is the how that instance was implemented. These can have farranging effects, even if they are not immediately identifiable from the outside. At any moment, an unstable atom can split depending on its neutron count, and at any moment, a poorly implemented instance of a design pattern may cause a crash, even though from the outside it looks fine.

将实现与外部接口分离的想法对于面向对象编程来说并不新鲜,它是非常自然和嵌入式的。同位素中引入的新概念以相同的方式分离概念的实现和接口。请记住,每个 design pattern 都有一系列角色(在 participants 部分中注明),这些角色必须由实现的各个部分来履行。这些角色是设计模式与其他概念和模式交互的外部接口。

The idea of separating the implementation from the external interface is not new to object-oriented programming, it is quite natural and embedded. The new concept introduced in isotopes is separating the implementation and interface of a concept in the same manner. Remember that each design pattern has a series of roles—noted in the participants section—that must be fulfilled by portions of an implementation. These roles are the external interface through which the design pattern interacts with other concepts and patterns.

协作部分描述了这些角色如何在概念级别进行内部交互,但这些交互可以独立于实现进行描述和讨论。这就是附录中的形式主义发挥作用的地方,它提供了极大的灵活性,但这里可以通过一个代码示例来说明这个概念。我们在 Section 2.2.2 中通过一个简单的方法传递性示例来暗示这一点,但现在我们可以更详细地介绍它。

The collaborations section describes how these roles interact internally at the conceptual level, but these interactions can be described and discussed independently of the implementation. This is where the formalisms from the appendix come into play and provide a tremendous amount of flexibility, but the concept can be shown here with a code example. We hinted at this back in Section 2.2.2 with a simple example of transitivity between methods, but now we can go into it in more detail.

从以下代码示例的左侧开始,其中对象具有 object 的方法调用方法 。我们说这依赖于 。这两个端点之间的对象、类型和方法相似性是明确定义的,并指定了它们之间的 EDP。如果更改了实现,例如,通过在方法调用链中注入新对象,则构造会更改,但端点之间的关系保持不变,如代码示例的右侧所示。最初的依赖是完整的,三个相似之处仍然完整。新的依赖肯定已经创建,但设计模式所需的原始依赖仍然存在。新的实现是原始 EDP 的同位素:从外部看,即使它的内部看起来不同,它看起来也一样,行为也一样。ffoobarbf.foob.bar

Start with the left side of the following code examples, where an object f has a method foo calling method bar of object b. We say that f.foo relies on b.bar. The object, type, and method similarities between those two endpoints is well defined and specifies the EDP between them. If the implementation is changed, for instance, by injecting a new object in a method-calling chain, the construct is altered, but the relationship between the endpoints remains unchanged, as in the right-hand side of the code example. The original reliance is intact, and the three similarities remain intact. New reliances have surely been created, but the original ones necessary for the design pattern remain. The new implementation is an isotope of the original EDP: it looks the same and acts the same when viewed as a concept from the outside, even if it looks different internally.

更重要的是,它允许开发人员讨论系统的设计,而不必关心每一个微小的实现细节。我们可以在更高的抽象层次上讨论设计。

More important, it allows developers to talk about the design of the system without having to be concerned with every tiny implementation detail. We can discuss the design at a higher level of abstraction.

例如,让我们重新审视 Fulfill Method 的构造,看看同位素如何通过这个小例子帮助提供灵活性。在该构造的最后,我提到图 4.2 中所示的 UML 是所需的最小 UML,而不是确切的要求。如果我们使用该确切的 UML 定义了 Fulfill Method,那么对实现 Fulfill Method 的方式的任何更改都需要为该模式提供新的定义。由于 Fulfill Method 在系统中的出现方式很多,因此这将导致如此简单的概念出现大量定义。这是次优的。

As an example, let’s revisit the construction of Fulfill Method and see how isotopes can help provide flexibility with even this small example. At the end of that construction, I mentioned that the UML shown in Figure 4.2 was the minimal UML required, not an exact requirement. Had we defined Fulfill Method using that exact UML, then any alteration to how we implemented Fulfill Method would require a new definition for the pattern. Because the number of ways that Fulfill Method could appear in a system are many, this would lead to a large number of definitions for such a simple concept. This is suboptimal.

Abstract Interface 中履行 Abstractor 角色的类可以具有任意数量的其他字段和与之关联的方法。它只需要至少有一个抽象的方法。图 4.8 显示了多个 UML 类,其中任何一个类都可以有一个与之关联的 Abstract Interface 实例。其中任何一个都可用于显示 Abstract Interface 实例的存在。一个类有多少个属性或字段,它可能有多少其他方法,或者它是模板还是泛型类,都无关紧要。该类只需要有一个抽象的方法。

The class that fulfills the Abstractor role in Abstract Interface can have any number of other fields and methods associated with it. It just has to have at least one method that is abstract. Figure 4.8 shows multiple UML classes, any one of which can have an Abstract Interface instance associated with it. Any one of these can be used to show the existence of an instance of Abstract Interface. It doesn’t matter how many attributes or fields a class has, how many other methods it may have, or whether it’s a template or generic class. The class simply has to have one method that is abstract.

图像

图 4.8.可以实现抽象接口 EDP 的替代类。

Figure 4.8. Alternative classes that can fulfill an Abstract Interface EDP.

换句话说,在图 4.4Fulfill Method 的图表中显示为 PINbox 的 Abstract Interface 实例可以代表其中任何一种或几乎无限数量的其他实现可能性。通过仅使用 PINbox,我们不必处理所有可能的 UML 表示或代码实现。

Stated differently, the instance of Abstract Interface shown as a PINbox in the diagram for Fulfill Method in Figure 4.4 can stand in for any of these or for a nearly infinite number of other implementation possibilities. By using just the PINboxes, we don’t have to deal with all the possible UML representations or code implementations.

同样,我们在图 4.2 中看到的双类、极简版本的 Inheritance 并不是满足该 EDP 的唯一方法。在我们感兴趣的两个类之间的继承链中可以有任意数量的类,如图 4.9 所示。继承树的深度无关紧要,因为子类仍然是子类。3 同样,图 4.4 中 PINbox 描述的 Inheritance 实例代表了这些可能的实现中的任何一种。

Likewise, the two-class, minimalist version of Inheritance we saw in Figure 4.2 isn’t the only way to satisfy that EDP. Any number of classes can be in the inheritance chain between the two classes we’re interested in, as shown in Figure 4.9. The depth of the inheritance tree doesn’t matter because a subclass is still a subclass.3 Again, the instance of Inheritance depicted by the PINbox in Figure 4.4 is representative of any of these possible implementations.

图像

图 4.9.可以满足继承 EDP 的替代结构。

Figure 4.9. Alternative structures that can fulfill an Inheritance EDP.

我们可以更进一步地介绍这个同位素概念。回想一下,并非每种语言都有显式类。这是 Section 2.2.2 中关于使用术语 type 的讨论的一个组成部分。子类化在此类语言中是如何工作的?我们在第 5 章Abstract Interface 规范中重新讨论了这一点,但即使是这种基于语言的重大变化也可以被同位素封装。请记住,我们不想关心实现语言可能是什么,这就是让我们这样做的原因。通过在 EDP 和 PIN 框级别工作,我们甚至将自己从可能用于实现的语言的语义中抽离出来。与语言无关是非常强大的。我们在这里或多或少地实现了设计概念的多态性。

We can take this isotope concept even a bit further. Recall that not every language has explicit classes. This was a component of the discussion for using the term type in Section 2.2.2. How does subclassing work in such languages? We revisit that in the specification for Abstract Interface in Chapter 5, but even that kind of significant language-based change can be encapsulated by an isotope. We do not want to have to care what the implementation language may be, remember, and this is what lets us do that. By working at the level of EDPs and PINboxes, we remove ourselves from even the semantics of the language that may be used for implementation. Being language-agnostic is extremely powerful. What we’ve accomplished here, more or less, is polymorphism of design concepts.

对于任何给定的设计模式,都有如此多的可能实现,您可能想知道我们如何知道设计模式的正确定义是什么。毕竟,我刚刚向您展示了,对于任何给定的设计概念,都可以存在大量可能的实现。模式社区仍然选择并写下一种规范的表单进行共享,那么选择表单的标准是什么呢?

With so many possible implementations for any given design pattern, you may be wondering how we know what the correct definition is for a design pattern. After all, I just showed you that for any given design concept, a huge number of possible implementations can exist. The patterns community still selects and writes down one canonical form for sharing, so what’s the criteria for selecting that one?

简而言之,转录的内容是最简单的实现以及参与者和协作的集合。最终的形式没有提供任何无关紧要的东西,并触及了所传达概念的本质。超出这个简单核心的任何内容都会混淆中心概念和教训。同位素几乎总是在规范描述中添加额外的项目,但以这样一种方式,这些概念仍然被体现出来。

Simply put, what is transcribed is the simplest possible implementation and set of participants and collaborations. The final form provides nothing extraneous and gets to the essence of the concept being communicated. Anything beyond that simple core would obfuscate the central concepts and lesson. Isotopes almost always add additional items to the canonical description, yet in such a way that the concepts are still embodied.

创建和编辑设计模式的方式是为了抓住问题的本质、解决方案和上下文,使教学、学习和使用它们尽可能简单。最后,最简单的描述就是最好的描述。让同位素处理 implementation 的修改。

Design patterns are created and edited in such a way as to get to the essence of the problem, the solution, and the context, to make teaching, learning, and using them as simple as possible. In the end, the simplest description is the best description. Let the isotopes handle the modifications of implementation.

4.2. 重新创建 Decorator

4.2. Recreating Decorator

到目前为止,在本章中,您已经了解了如何以格式良好且具体的方式组合非常小的概念和设计问题,以形成较大的概念。您还了解了这些概念是如何成为实现的封装的,因此可以通过抽象来简化设计讨论。我们将结合这些元素来构建一个比第 2 章开始时更令人满意和更健壮的 Decorator 定义。

So far in this chapter, you’ve seen how very small concepts and design issues can be composed in well-formed and specific ways to form larger concepts. You’ve also seen how these concepts are an encapsulation of the implementation, so the design discussion can be simplified through abstraction. We’re going to combine these elements to build up a much more satisfying and robust definition of Decorator than we started with in Chapter 2.

我们首先回顾一下图 4.10Decorator 的初始 UML 图和图 4.11Fulfill Method 的 UML 版本。

We start by revisiting our initial UML diagram for Decorator in Figure 4.10 and our UML version of Fulfill Method in Figure 4.11.

图像

图 4.10.Decorator 的常用示例 UML。

Figure 4.10. Decorator’s usual example UML.

图像

图 4.11.将 Method 定义实现为带注释的 UML。

Figure 4.11. Fulfill Method definition as annotated UML.

再看一下 Decorator图 4.10),我们可以看到 Fulfill Method 的结构体在两个地方,Component 类在这两个地方都充当 Abstractor 角色。仔细想想,这看起来和图 2.2 中的 Objectifier 模式很像,不是吗?好眼睛。这正是 Objectifier 的含义 — 将单个 Fulfill Method 扩展到多个具体子类,如图 4.12 所示。同样,我们使用 Fulfill Method 的 PIN,此处以堆叠形式显示(参见 Section 3.2.4)来指示基本概念如何组合在一起。我们刚刚定义了一个已经存在于文献中的设计模式,但从基本原则来看,它是由更小的、易于理解的部分构建的。在图 4.2图 4.5 中,我们将 Fulfill Method 包装在一个 PIN 框中。我们使用图 4.12Objectifier 进行相同的包装,但省略了两者之间的图表。这为我们提供了一个 Objectifier 的 PINbox。

Looking again at Decorator (Figure 4.10), we can see where the structure from Fulfill Method is found, in two places, with the Component class acting as the Abstractor role in both. On second thought, this is looking quite like our Objectifier pattern from Figure 2.2, isn’t it? Good eye. This is exactly what Objectifier is—the expansion of a single Fulfill Method across several concrete subclasses, as in Figure 4.12. Again, we’re using the PIN for Fulfill Method, shown here in its stacked form (see Section 3.2.4) to indicate how the underlying concepts fit together. We just defined a design pattern that already existed in the literature, but from first principles, by building it out of smaller, well-understood pieces. In Figures 4.2 through 4.5, we wrapped Fulfill Method in a PINbox. We do the same wrapping of Objectifier using Figure 4.12 as the internals but omitting the diagrams in between. This gives us a PINbox for Objectifier.

图像

图 4.12.对象化器用 PIN 批注的 UML。

Figure 4.12. Objectifier UML annotated with PIN.

更进一步,我们还可以定义 Object Recursion。回想一下,我们在 2.2.1 节中说过 ObjectifierObject Recursion 的一个组件,但我们没有讨论其余部分是什么。现在,我们可以将其标识为 Trusted Redirection EDP。图 4.13 将这两种模式显示为 UML。在组合形成对象递归时,并将合并,如 will 和 .FamilyHeadObjectifierConcreteObjectifierBRedirector

Taking this a step further, we can define Object Recursion as well. Recall that we said in Section 2.2.1 that Objectifier is a component of Object Recursion, but we did not address what the remaining part was. We can now identify it as the Trusted Redirection EDP. Figure 4.13 shows the two patterns as UML. In combining to form Object Recursion, FamilyHead and Objectifier will merge, as will ConcreteObjectifierB and Redirector.

图像

图 4.13.ObjectifierTrusted Redirection 的 Trusted Redirection

Figure 4.13. Objectifier and Trusted Redirection.

图 4.14 显示了对象递归,其形式比原始图 2.3 中介绍的形式略清晰,现在用 ObjectifierTrusted Redirection 的 PINbox 进行注释。Objectifier 模式中的 Objectifier 角色Trusted Redirection 中的 Family Head 角色现在由同一实体(名为 .同样,已合并到 .图 4.15 中的 PIN 图更简洁地说明了这一点。HandlerConcreteObjectifierBRedirectorRecursor

Figure 4.14 shows Object Recursion in a slightly cleaner form than as introduced in the original Figure 2.3, now annotated with the PINboxes for Objectifier and Trusted Redirection. The Objectifier role from the Objectifier pattern and the Family Head role from Trusted Redirection are now being fulfilled by the same entity, the class named Handler. Likewise, ConcreteObjectifierB and Redirector have merged into Recursor. This is shown more succinctly by the PIN diagram in Figure 4.15.

图像

图 4.14.使用 PIN 注释的对象递归

Figure 4.14. Object Recursion annotated with PIN.

图像

图 4.15.Object Recursion 作为 PIN。

Figure 4.15. Object Recursion as just PIN.

我们现在可以从更小的模式的角度来讨论对象递归,并且方式比以前更精确。“Object Recursion 通过 Objectifier 模式使用多态性来确定在运行时相关类型系列中的哪种类型将处理特定调用。通过将可信重定向应用于至少一个可能的实现,它还将该系列中的两个或多个对象链接在一起,以便它们可以依次处理相同的调用并引入自己的实现。这个陈述是精确、直接的,它避免了从结构性的角度讨论模式。此外,如果有人不清楚基本概念,他或她可以研究每个子模式的完整模式规范。我们可以利用模式文献中的这些规范和定义,使我们对更高级别抽象和模式的描述更容易理解。

We can now discuss Object Recursion in terms of smaller patterns and in a much more precise way than we could before. “Object Recursion uses polymorphism, through the Objectifier pattern, to determine at runtime which type in a related family of types will handle a specific call. By applying Trusted Redirection to at least one of the possible implementations, it also chains together two or more objects from that family such that they can handle that same call in turn and bring their own implementation to bear.” This statement is precise, it is direct, and it avoids having to discuss the pattern in structural terms. Furthermore, if someone is unclear on the underlying concepts, he or she can study the full pattern specifications of each of the subpatterns. We can leverage those specifications and definitions from the patterns literature to make our descriptions of higher-level abstractions and patterns much easier to comprehend.

我们组成了 Decorator 模式的上半部分,只剩下一块来完成我们的定义。剩下的概念是 Extend Method,它通过扩展 Trusted Redirection 来填充 Decorator 的底部,如图 4.164.17 所示。Extend Method 的原始行为和 Object Recursion 中的解析器合并到 Decorator 中,将这两个较小的模式联系在一起。4

We composed the top half of the Decorator pattern and have just one piece left to go to finish our definition. The remaining concept is Extend Method, which fills out the bottom of Decorator by extending Trusted Redirection, as shown in Figures 4.16 and 4.17. Extend Method’s original behavior and recursor from Object Recursion merge into Decorator, tying these two smaller patterns together.4

图像

图 4.16.Object RecursionExtend 方法。

Figure 4.16. Object Recursion and Extend Method.

图像

图 4.17.使用 PIN 注释的 Decorator

Figure 4.17. Decorator annotated with PIN.

我们可以通过再次将其简化为仅 PIN 框来简化它,如图 4.18 所示。此外,我们总是可以将其包装并简化为一个 PINbox,表示 Decorator 的实例,如图 4.19 所示。这里的角色名称直接取自 Gang of Four (GoF) 文本 [21] 中 Decorator 规范的 participants 部分,并增加了两个内容:operation 和 componentObj。这些在 participants 部分进行了隐含的讨论,但我们在这里明确它们以澄清所涉及的部分。

We can simplify this by reducing it again to just the PINboxes, as in Figure 4.18. Furthermore, we can always wrap and reduce this to a single PINbox indicating an instance of Decorator, as in Figure 4.19. The role names here are taken directly from the participants section of the Decorator specification in the Gang of Four (GoF) text [21], with two additions: operation and componentObj. These are implicitly discussed in the participants section, but we make them explicit here to clarify the pieces involved.

图像

图 4.18.Decorator 作为 PIN。

Figure 4.18. Decorator as PIN.

图像

图 4.19.Decorator 实例作为 PINbox 实例。

Figure 4.19. Decorator instance as a PINbox.

现在,我们对 Decorator 模式有一个简单、简洁的表示法。然而,在这一点上,我们也可以走另一条路。我们可以使用扩展的 PINbox 通过显示概念的底层层次结构,在 Decorator 中越来越多地公开更精细的粒度。图 4.20Decorator 显示为扩展的 PINbox,显示了其直接的内部布线。图 4.214.22 分别深入研究了 Object RecursionObjectifier,最后图 4.23Fulfill Method 扩展到了 EDP 级别,此时我们不能进一步分解。Decorator 现已完全显示。这些关系图中的每一个都等效于其他关系图。

Now we have a simple, concise notation for the Decorator pattern. At this point, however, we can go the other way as well. We can use expanded PINboxes to increasingly expose finer granularity in Decorator by showing the underlying hierarchy of concepts. Figure 4.20 shows Decorator as an expanded PINbox, revealing its direct internal wiring. Figures 4.21 and 4.22 drill into Object Recursion and Objectifier, respectively, and finally Figure 4.23 expands Fulfill Method to the EDP level, at which point we can decompose no further. Decorator is now fully revealed. Each of these diagrams is equivalent to the others.

图像

图 4.20.扩展 Decorator:一级。

Figure 4.20. Expanding Decorator: one level.

图像

图 4.21.扩展 Decorator:两个级别。

Figure 4.21. Expanding Decorator: two levels.

图像

图 4.22.扩展 Decorator:三个级别。

Figure 4.22. Expanding Decorator: three levels.

图像

图 4.23.扩展 Decorator:四个级别。

Figure 4.23. Expanding Decorator: four levels.

这就是 Decorator,我们只用四个 EDP 构建了它:Abstract InterfaceInheritanceTrusted RedirectionExtend Method。每个概念都是一个简单的概念,但是它们以非常具体的组合形式联系在一起,描述了软件系统中常见的相当高级的抽象。更重要的是,我们证明了可以学习和掌握过渡概念,以更全面地理解 Decorator

So that’s Decorator, and we built it with just four EDPs: Abstract Interface, Inheritance, Trusted Redirection, and Extend Method. Each is a simple concept, but together, linked in a very specific combination, they describe a fairly high-level abstraction that is commonly found in software systems. What’s more, we demonstrated that interim concepts can be studied and mastered to gain a more thorough understanding of Decorator.

最重要的是,我们从未讨论过代码。我们没有提出类、方法或字段。我们只讨论了概念和想法,但我们实现了一个提供指导和精确的框架。

Most important, we never once discussed code. We didn’t bring up classes, or methods, or fields. We talked about concepts and ideas only, yet we achieved a framework that provides guidance and precision.

在 EDP 中,我们拥有构建我们理解的出色软件的构建块。

In EDPs, we have the building blocks with which to form great software that we understand.

GoF 文本的最后一部分 [21, p. 358] 引用了 Christopher Alexander 的一句话来描述什么是“好设计”。当他们被看作是 EDP 的燕尾榫和交织的例子时,我想不出比这更合适的描述他们自己的设计模式了。

The final section of the GoF text [21, p. 358] uses a quote from Christopher Alexander to describe what is “good design.” I can think of no more fitting description of their own design patterns when viewed as dovetailed and intertwined examples of the EDPs.

可以通过以相当松散的方式将模式串在一起来制作建筑物。像这样建造的建筑,是模式的集合。是不是很密集。它并不深刻。但是,也可以将模式放在一起,使许多模式在同一个物理空间中重叠:建筑物非常密集;它在狭小的空间里捕捉到了许多含义;通过这种密度,它变得深奥。[3, 第 xli 页]

It is possible to make buildings by stringing together patterns, in a rather loose way. A building made like this, is an assembly of patterns. Is it not dense. It is not profound. But it is also possible to put patterns together in such a way that many patterns overlap in the same physical space: the building is very dense; it has many meanings captured in a small space; and through this density, it becomes profound. [3, p. xli]

设计模式文献通过以密集而精确的方式组合较小的概念,充满了深度。EDP 让我们能够清晰而有洞察力地查看该密度。

The design patterns literature is full of profundity through the composition of smaller concepts in dense and precise ways. EDPs let us view that density with clarity and insight.

再多一条评论。您是否还记得第 2 章开头关于工业代码中隐藏的 Decorator 模式的示例,该模式激发了 SPQR 的灵感?SPQR 使用此处描述的 EDP、合成技术和形式主义,能够在短短几秒钟内识别出它的存在,而无需得到任何提示。与近 200 小时相比,可以利用很多咖啡休息时间。

One more comment. Do you recall the example from the beginning of Chapter 2 about the hidden Decorator pattern in industrial code that inspired SPQR? SPQR, using the EDPs, composition technique, and formalisms described here, was able to identify its existence in just a couple of seconds without being given any hints. Compared to almost 200 hours, that’s a lot of coffee breaks that could be taken instead.

 

 

4.3. 重构

4.3. Refactoring

Section 4.1.1 中讨论同位素时,我指出了一个模式同位素实例从外面看可能与该模式的另一个实例相同,但有一个错误的实现,会在以后引起问题。然而,比单个实现不佳的设计模式更糟糕的是,拥有多个交互的设计模式,这些模式都为系统带来了善意和合理的实现选择,但当组合在一起时就会形成问题。回想一下,第 2.1 节中描述的 SPQR 的最初驱动问题是设计模式是有机地和无意中出现的。恶性设计同样容易形成,而且由于生成不良代码的方法数量大于良好设计的数量,因此它们往往更容易出现。

In discussing isotopes in Section 4.1.1, I stated that a pattern isotope instance might look the same as another instance of that pattern from the outside but have a buggy implementation that causes problems later. Worse than a single poorly implemented design pattern, however, is having multiple interacting design patterns that all bring well-meaning and rational implementation choices to the system but form a problem when combined. Recall that the initial driving problem for SPQR, described in Section 2.1, was that a design pattern arose organically and unintentionally. Malignant designs can form just as easily, and because the number of ways to produce poor code is greater than the number of good designs, they tend to arise much more readily.

当涉及到设计模式时,我们可以对可能出现的一些 “糟糕的设计” 问题进行分类。

When it comes to design patterns, we can classify some of the “bad design” issues that can appear.

首先是反模式,你可能听说过 [14]。这些是应尽可能避免的模式。它们表达的是常见的不良做法,而不是最佳做法。如果它们存在于您的系统中,您应该努力删除它们并找到合适的设计模式来解决用例。

First are the anti-patterns, which you may have heard of [14]. These are patterns that should be avoided whenever possible. They express common poor practices, not best practices. If they exist in your system, you should work to remove them and find a proper design pattern to address the use case instead.

接下来是被误用的适当设计模式。也许它们只是没有正确实施并且缺少关键部分。也许设计者或实现者没有理解它们,也没有完全形成。这些部分设计模式通常离表达原始模式只有一两个小的调整。

Next up are proper design patterns that are misapplied. Perhaps they simply weren’t implemented correctly and are missing critical pieces. Perhaps they weren’t understood by the designer or implementer and aren’t quite fully formed. These partial design patterns often are just one or two small tweaks away from expressing the original pattern.

另一方面,恶性模式是增长但增长缓慢的模式。在它们曾经被正确应用和实现的地方,随着时间的推移,代码修改已经将它们分解或变异为不再满足其最初目的的形式。在这种情况下,最糟糕的副作用是文档(如果存在)仍然会经常引用它们,从而误导开发人员。

Malignant patterns, on the other hand, are patterns that grew, but grew poorly. Where they may once have been applied and implemented correctly, code modifications over time have broken or mutated them into forms that no longer fulfill their original purpose. The worst side effect in this case is that documentation, if it exists, will frequently still refer to them and thereby mislead developers.

最后,医源性模式是为解决现有问题而正确实施的设计模式,正如在选择解决方案时所理解的那样。不幸的是,更大的设计问题带来了被忽视或未知的力和约束。该模式解决了最初的问题,但通过与这些其他上下文力量的相互作用,无意中创造了一个可能比原来的更糟糕的新情况。

Finally, iatrogenic patterns are design patterns that were implemented correctly to solve an existing problem, as it was understood when the solution was selected. Unfortunately, larger design issues brought to bear forces and constraints that were ignored or unknown. The pattern solved the original problem but, by interacting with these other context forces, inadvertently created a new situation that may be worse than the original.

所有这些问题都可以通过正确重构代码来解决,这主要需要将功能从一个位置移动到另一个位置,以更好地简化设计。系统的特性和可执行功能没有明显的改变,但各个部分连接在一起的方式却发生了明显的变化。您不是直接更改代码正在执行的操作,但您可能正在增强代码可以执行的操作。通过改进系统的设计,可以提高系统的可读性,这意味着开发人员更容易理解和修改代码。添加新功能、修复错误和许多其他理想的操作更简单、更快速地执行和完成。

All of these problems can be solved by properly refactoring the code, which mainly requires moving functionality from one location to another to better streamline a design. The features and executable functionality of the system are not appreciably altered, but the ways the pieces hook together are. You’re not directly altering what the code is doing, but you may be enhancing what the code could do. By improving the design of the system, you’re improving the readability of the system, which means that it is easier for a developer to understand and modify the code. Adding new features, fixing bugs, and many other desirable actions are simpler and faster to undertake and complete.

代码重构在我们的领域已经很成熟,特别是两个来源值得指出。Martin Fowler 的 Refactoring [19] 和 Joshua Kerievsky 的 Refactoring to Patterns [24] 构成了一组将代码从一种配置转换为另一种配置的燕尾合方法。两者都非常值得您花时间检查。如果您曾经使用过 Eclipse、Visual Studio 或 Xcode 等集成开发环境 (IDE),则您可能已经通过这些工具中提供的功能使用了重构。它们自动执行执行简单、有用的任务的过程,而 Refactoring 是大部分此类功能的起点。如果您想深入了解如何最好地使用自动化工具,那么这些书绝对不会出错。

Refactoring of code is well established in our field, and two sources in particular are worth pointing out. Martin Fowler’s Refactoring [19] and Joshua Kerievsky’s Refactoring to Patterns [24] form a dovetailed set of recipes for converting code from one configuration to another. Both are well worth your time to check out. If you’ve ever used an integrated development environment (IDE) such as Eclipse, Visual Studio, or Xcode, you have probably used refactorings via the capabilities provided in those tools. They automate the process of performing simple, useful tasks, and Refactoring was the starting point for much of this functionality. If you want a solid understanding of how best to use the automated tools, you can’t go wrong with these books.

Fowler 的文字为一系列小型、离散的重构操作奠定了基础,您可以采取这些操作来改进代码设计。例如,他从 “难闻的味道” 开始,在这种情况下,即使代码可能执行正常,您也知道代码有些地方不太对劲。他将这些直觉描述为潜意识中对代码设计感到不安的可能迹象,以及它如何易于理解或清晰地表达出来。你有更多的编程经验,对良好设计原则的接触越多,你就越能学会在这些情况下相信自己的直觉。

Fowler’s text lays the groundwork for a catalog of small, discrete refactoring actions that you can take to improve your code design. For instance, he starts with “bad smells,” which are situations in which you know something is not quite right with the code even though it may perform correctly. He describes these gut feelings as possible indications of a subconscious uneasiness with the code design, with how understandable or cleanly expressed it is. The more experience you have with programming and the more exposure to good design principles, the more you’ll learn to trust your instincts in these cases.

对于每一种难闻的气味,Fowler 都会提供一系列措施来缓解问题。这些行动范围广泛且多样,但我在这里只关注三个。

For each bad smell, Fowler provides a series of actions to alleviate the problem. These actions are wide ranging and varied, but I focus on only three here.

例如,Extract Method 是一个重构操作,可用于解决涉及太长且难以理解的方法的味道。在最简单的形式中,它指导您将 long 方法的自包含部分提取到其自己的方法中并调用它。新方法的命名具有重要意义,此时您不应该感到惊讶。

Extract Method, for example, is a refactoring action that can be used to solve a smell involving a method that’s too darned long and difficult to understand. In its simplest form, it directs you to extract a self-contained portion of the long method into its own method and call it. Great significance is given to the naming of the new method, which shouldn’t surprise you by this point.

Rename Method 中体现了这一重要性,用于在方法的有用性或适用性可能被混淆时阐明意图。

This significance is exemplified in Rename Method, used to clarify intent when a method’s usefulness or applicability may be obfuscated.

同样,Move Method 用于阐明哪些方法属于一起。正如 Fowler 所说,当“一个方法正在或将要使用另一个类的特征或被另一个类的特征所使用,而不是定义它的类时,需要进行澄清。换句话说,当由于某种原因,该方法位于错误的类中时,请使用 Move Method

Similarly, Move Method is used to clarify which methods belong together. Clarification is needed when, as Fowler states, “a method is, or will be, using or used by more features of another class than the class on which it is defined.” In other words, use Move Method when, for some reason, the method is in the wrong class.

现在,这些可能看起来微不足道,但每一个都有实现的微妙之处,这使得 Fowler 的贡献成为必要的,理所当然地成为我们领域的经典。更重要的是,Fowler 使用这些微小的、格式正确的操作作为构建块,用于更复杂的更大规模重构。Kerievsky 采用这个概念并与之一起运行,使用 Fowler 的目录作为更丰富的重构的种子,这些重构展示了如何有条不紊地、精确地迁移到更常见的设计模式的实现。听起来很熟悉?

Now, these may seem trivial, but each one has implementation subtleties that make Fowler’s contribution necessary and rightfully a classic in our field. More to the point, Fowler uses these tiny, well-formed actions as building blocks for larger refactorings that are more complex. Kerievsky takes this concept and runs with it, using Fowler’s catalog as the seed for even richer refactorings that show how to methodically and precisely migrate toward implementations of the more common design patterns. Sound familiar?

这里有一个有趣的相似之处。我们可以通过考虑 Extract Method 重构来继续它。此重构的效果是创建一个新方法和对该方法的新调用。从中提取代码块的方法是调用站点,新方法是被调用方。显然,这两种方法不可能相同,5 因此我们在此时假设这两种方法不同。此外,由于新方法在相同的类/类型中形成(根据重构定义),因此我们具有相同的类型和相同的对象。好吧,我们从 2.2.4 节的讨论中知道了这是什么。具有相同对象、相同类型但方法不同的 EDP 是 Conglomeration

An interesting parallel is at work here. We can continue it by considering the Extract Method refactoring. The effect of this refactoring is to create a new method and a new call to that method. The method that the chunk of code is extracted from is the calling site, and the new method is the callee. Obviously, the two methods can’t be the same,5 so we assume at this point that the methods are dissimilar. Also, because the new method is being formed in the same class/type—according to the refactoring definition—we have the same type and also the same object. Well, we know what this is from our discussion in Section 2.2.4. The EDP with the same object, same type, but dissimilar method is Conglomeration.

接下来,让我们思考 Rename Method 以及它对重命名方法所涉及的 EDP 可能意味着什么。请记住,方法相似性(部分)由方法的名称决定。如果我们更改名称,我们可能会从方法相似的 EDP 转变为方法不同的等效 EDP。例如,考虑一个 Delegation 实例,如图 4.24 所示。如果目标方法被重命名为 ,它现在类似于调用站点,并且 Delegation 已变为 Redirection,如图 4.25 所示。当然,重命名可以是任何内容,并且此 EDP 转换仅在新名称相似匹配时成立。所以现在我们可以说 Delegation + Rename Method (to similarity) = Redirection。反过来说,重定向 + 重命名方法 = 委派called()caller()

Next, let’s ponder Rename Method and what it might mean for an EDP that the renamed method is involved in. Remember, method similarity is (partially) determined by the name of the method. If we alter the name, we could be moving from a method-similar EDP to its method-dissimilar equivalent. For example, consider an instance of Delegation, as in Figure 4.24. If the target method called() is renamed to caller(), it now is similar to the calling site, and Delegation has become Redirection, as in Figure 4.25. Of course, the rename could be to anything, and this EDP transformation only holds if the new name is a similarity match. So now we can say that Delegation + Rename Method (to similarity) = Redirection. Going the other way, Redirection + Rename Method = Delegation.

图像

图 4.24.Rename Method 重构之前的委派

Figure 4.24. Delegation before Rename Method refactoring.

图像

图 4.25.重命名方法重构后的委派 - 重定向

Figure 4.25. Delegation after Rename Method refactoring—Redirection.

随着设计的每一次更改,无论多么小,我们都会改变作为其组件的 EDP。EDP 反过来会影响由它们形成的设计模式,依此类推,直到组合层次结构。通过精确地了解重构文献中列举的那些更改将如何在多个粒度级别上影响我们的设计,并精确地知道这些重构将如何渗透到整个系统中的更改,我们可以更好地预测计划的设计更改的更大后果。

With each change in the design, no matter how small, we alter the EDPs that are components of it. The EDPs will in turn affect the design patterns that are formed from them, and so on up the compositional hierarchy. By knowing precisely how alterations such as those enumerated in the refactoring literature will affect our design on multiple levels of granularity, and by knowing precisely how those refactorings will percolate changes throughout our system, we can better predict what the larger ramifications of a planned design change will be.

但是,不需要进行重大更改即可大幅更改设计或触发连锁反应。我们通过应用 Rename MethodDelegation 转换为 Redirection,这样它就创建了以前没有的方法相似性。让我们撤销这个相似性的变化,但从 Trusted Redirection 开始,我们在 Section 4.2 中用它来帮助创建 Decorator。如果我们对 Trusted Redirection 中使用的方法执行 Rename Method 重构,您认为会发生什么情况?

Large changes aren’t required to substantially alter a design, however, or to trigger a ripple effect. We transformed Delegation to Redirection by applying Rename Method such that it created a method similarity where there wasn’t one before. Let’s reverse that similarity change but start with Trusted Redirection, which we used to help create Decorator back in Section 4.2. What do you think happens if we perform a Rename Method refactoring on the method used in Trusted Redirection?

简单。Decorator 的实例不复存在。

Simple. The instance of Decorator ceases to exist.

通过重命名方法,无论在实现中的嵌套深度如何(这是可能的),因为同位素的原因,我们都会删除 Trusted Redirection 并将其替换为其方法不同的等效项 Trusted Delegation。当我们删除 Trusted Redirection 时,我们会删除一个核心且必要的 Decorator 部分。我们把它变成了什么?问得好。在某些方面,新设计看起来有点像 Strategy,这是 GoF 中的另一种设计模式,与 Decorator 在概念上有一些相似之处,但无论如何,我们的 Decorator 已经不复存在了。如果我们对系统的文档或期望包括该实例的存在,那么我们现在对系统的理解与它在实现中的实际存在之间存在不匹配。那是错误的根源。

By renaming the method, no matter how deeply nested in the implementation—which is possible because of isotopes—we remove Trusted Redirection and replace it with its method-dissimilar equivalent, Trusted Delegation. When we remove Trusted Redirection, we remove a central and necessary piece of Decorator. What have we turned it into? Good question. In some ways, the new design looks a bit like a Strategy, another design pattern from the GoF that shares some conceptual similarities with Decorator, but in any case, our Decorator is no more. If our documentation or expectations about the system include that instance being there, we now have a mismatch between our understanding of the system and how it actually exists in implementation. That is a recipe for mistakes.

为了更有趣,让我们考虑一下 Move 方法,以及它与移动时调用它的任何方法之间的依赖关系会发生什么变化。首先,我们必须认识到移动方法意味着它将被移动到新的类/类型。处于 new 类型中意味着调用它的任何方法都可能必须进行一些清理,以便在移动完成后继续使用它。让我们再次从一个使用 Delegation 的简单示例开始,如图 4.26 所示。提醒一下,这是两个不同方法之间的方法调用,包含在两个不同且不相关的类型的不同对象中。我们知道应用 Rename Method 会更改方法相似性。但是,随着方法的移动,这个 EDP 将如何变化呢?

For more fun, let’s consider Move Method and what happens to the reliances between it and any method that calls it when it is moved. First, we have to recognize that moving the method means that it is going to be moved to a new class/type. Being in a new type means that any method that calls it may have to do some cleanup to continue using it after the move is accomplished. Let’s start with a simple example using Delegation again, as shown in Figure 4.26. As a reminder, this is a method call between two dissimilar methods contained in two different objects of different and unrelated types. We know that applying Rename Method changes the method similarity. But how will this EDP change as we move the method around?

图像

图 4.26.Move Method 重构之前的 delegation

Figure 4.26. Delegation before Move Method refactoring.

此重构具有许多可能的效果,具体取决于其实现方式。其范围和影响足够多,现在是简要介绍另一半 EDP 的好时机。回想一下第 2.2.4 节中图 2.10 中方法相似的 EDP 的二维网格。在图 4.27 中,你会发现它们的方法不同。对象类型轴被翻转以完全镜像第一个图,因为这些与我们之前讨论的等效,除了方法相似性。

This refactoring has a number of possible effects, depending on how it is implemented. The range and impacts are numerous enough that now is a good time to briefly introduce the other half of the EDPs. Recall from Section 2.2.4 the two-dimensional grid of method-similar EDPs in Figure 2.10. In Figure 4.27, you’ll find their method-dissimilar counterparts. The object type axis is flipped to exactly mirror the first figure, because these are equivalent to the ones we discussed before, except for the method similarity.

图像

图 4.27.方法相似性固定为非相似性的设计空间。

Figure 4.27. The design space with method similarity fixed to dissimilar.

在移动一个方法时,最常见的情况是将其移动到一个相关的类型,如图 4.28 所示。这样的移动保留了 Delegation 实例,但不是很有趣。相反,考虑一下当我们将方法移动到与调用方法相同的类类型时会发生什么,如图 4.29 所示。当然,现在调用 Object 没有多大意义。called()obj

In moving a method, the most general case is to move it to a related type, as in Figure 4.28. Such a move retains the Delegation instance, but it isn’t very interesting. Consider instead what happens when we move the method into the same class type as the calling method, as in Figure 4.29. Of course, now calling called() on the obj object doesn’t make much sense.

图像

图 4.28.Move Method 重构后的委托:无聊的情况。

Figure 4.28. Delegation after Move Method refactoring: boring case.

图像

图 4.29.Move Method 重构后的委派:转换为同一类型。

Figure 4.29. Delegation after Move Method refactoring: into same type.

有两种方法可以修复 method 中的不匹配。首先,可以选择将对象的类型更改为目标类型。换句话说,成为与定义它的类型相同类型的实例字段。其次,可以完全消除对象并使用 .采用哪种路径取决于 的数据使用需求。如果主要处理通过 传递给它的数据,并且该数据是对象封装中的实例数据,则该数据可以直接访问,不再需要作为参数传递。在这种情况下,第二个选择是好的。否则,首选前一种设计。caller()caller()objobjcaller()selfcalled()called()caller()caller()called()

There are two possibilities for fixing the mismatch within the method caller(). First, the caller() can elect to change the type of the object obj to the destination type. In other words, obj becomes an instance field of the same type as the type it is defined in. Second, caller() can eliminate the object completely and call its own instance using self. Which path is taken depends on the data usage needs of called(). If called() works primarily on data that is passed to it via caller(), and that data is instance data in the object encapsulating caller(), then that data can be accessed by called() directly and no longer needs to be passed as parameters. In this case, the second choice is a good one. Otherwise, the former design is preferred.

但是,哪些 EDP 代表这两种情况呢?

But what EDPs represent these two cases?

第一种选择是更改对象的类型,这会导致同一类型的不同对象的不同方法之间的方法调用 EDP。通过查看图 4.27,作为不同的对象,相似的类型,我们可以看到这是一个委托式集群的例子。与 Redirected Recursion 非常相似,它涉及两个相同类型的对象,协同工作。但是,与 Redirected Recursion 不同的是,在这种情况下,这两种方法不同。图 4.30 说明了这种情况。

The first choice, changing the type of the object, results in a method-call EDP between differing methods of differing objects of the same type. By looking at Figure 4.27, as dissimilar object, similar type, we can see that this is an example of a Delegated Conglomeration. Much like Redirected Recursion, it involves two objects of the same type, working in concert. Unlike Redirected Recursion, however, in this case the two methods are dissimilar. Figure 4.30 illustrates this situation.

图像

图 4.30.Move 方法重构后的委托委托的 Conglomeration

Figure 4.30. Delegation after Move Method refactoring: Delegated Conglomeration.

另一种方法是简单地将 call-upon 对象替换为 ,无需 field 对象,如图 4.31 所示,并产生与以前相同的 dissimilar-method 关系,但现在具有相似的类型和类似的对象。 图 4.27 告诉我们这是 Conglomeration,我们在 2.2.4 节中讨论过。self

The alternative is to simply replace the called-upon object with self, eliminating the need for the field object, as shown in Figure 4.31, and resulting in the same dissimilar-method relationship as before, but now with a similar type, and a similar object. Figure 4.27 tells us that this is Conglomeration, which we discussed back in Section 2.2.4.

图像

图 4.31.Move 方法重构后的委派Conglomeration

Figure 4.31. Delegation after Move Method refactoring: Conglomeration.

类似的二元性也发生在其他与类型相关的决策中。如果要移动的方法被移动到 的超类型,那么我们有 Trusted DelegationRevert Method,这取决于被调用的对象是保留新类型还是被消除以支持 super图 4.324.33 展示了这些结果。Trusted Delegation 非常常见,当将委托交给相关类型的受信任组并得到适当处理时,就会出现 Trusted Delegation。这是最简单的,方法是对类型是调用站点的超类的对象使用多态调用。超类提供受信任的类型组,而多态性意味着它得到了适当的处理。CallingClass

A similar duality occurs in other type-related decisions. If the method being moved is moved to a supertype of CallingClass, then we have either Trusted Delegation or Revert Method, depending on whether the called-on object is retained with the new type or eliminated in favor of super. Figures 4.32 and 4.33 demonstrate these outcomes. Trusted Delegation is extremely common and appears when the delegation is to be handed to a trusted group of related types and handled appropriately. This is easiest to do by using a polymorphic call on an object whose type is a superclass of the calling site. The superclass provides the trusted group of types, and the polymorphism means that it is handled appropriately.

图像

图 4.32.Move 方法重构后的委派受信任的委派

Figure 4.32. Delegation after Move Method refactoring: Trusted Delegation.

图像

图 4.33.Move Method 重构后的委派Revert Method

Figure 4.33. Delegation after Move Method refactoring: Revert Method.

将方法移动到同级类型会导致如图 4.34 所示的情况。如您所料,这是 Deputized Delegation 的一个例子。在这里,通过将多态根限制为调用站点的同级类来优化可能的处理类型的可信组。calledCallingClass

Moving the method called to a sibling type of CallingClass results in a situation such as in Figure 4.34. As you might expect, it is an example of Deputized Delegation. Here, the trusted group of possible handling types is refined by restricting the polymorphic root to a sibling class of the calling site.

图像

图 4.34.Move 方法重构后的委派委派。

Figure 4.34. Delegation after Move Method refactoring: Deputized Delegation.

最后,应该注意的是,每个转换都有一个相反的对称转换。我们可以轻松地从我们转换的任何 EDP 返回到 Delegation,然后返回到任何其他端点 EDP。在此过程中,我们遍历了整个图 4.27。通过对 Move Method 进行一次简单的重构,我们在所描述的所有六个 EDP 之间进行路由。

Finally, it should be noted that each of the transformations has a symmetric transformation that reverses it. We could just as easily go back to Delegation from any of the EDPs we transformed into and then on to any other endpoint EDP. In doing so, we traverse the entirety of Figure 4.27. With that one simple refactoring of Move Method, we routed between all six EDPs described.

如您所见,重构可以在设计中产生深远的影响,即使所采取的行动可能看起来微不足道。在进行重构工作时,请务必记住您的设计将在哪里结束,EDP 可以帮助您提前规划。

As you can see, refactoring can have far-reaching effects in a design, even when the actions taken may seem trivial. It is important to keep in mind where your design is going to end up when undertaking refactoring efforts, and EDPs can help you plan ahead.

我们可以总结到目前为止讨论的操作和生成的 EDP 转换,如图 4.35 所示。您可以看到如何应用 Move Method(从 Delegation(委派)开始)通过粗体路径将我们引导到图表左侧的其他五个 EDP。我已经填写了其余可能的转换,包括它们的镜像转换。使用重构的 Rename Method 向左和向右移动暗示了这里的对称性,我们将在下一节中重新讨论。

We can summarize the actions and resulting EDP transformations that we’ve discussed so far, as shown in Figure 4.35. You can see how applying Move Method, starting from Delegation, leads us to each of the other five EDPs on the left side of the diagram though the paths in bold. I’ve filled in the remaining possible transformations, including their mirror transformations. The use of the refactoring Rename Method to move left and right hints at a symmetry here, which we revisit in the next section.

图像

图 4.35.到目前为止,总结了重构效果。

Figure 4.35. Summarizing refactoring effects so far.

4.4. 大局观

4.4. The Big Picture

到目前为止,本章是一系列有关使用 EDP 的课程。您学习了如何将 EDP 和其他模式组合成具有可靠且可行的定义的更大抽象。您学习了在阅读新模式时如何查找您已经知道的模式。我们讨论了同位素,它允许设计模式及其实现在一定程度上解耦,从而允许封装设计概念和在代码中灵活表达。您甚至了解了已建立的重构方法库,了解了它们如何与 EDP 协同工作,并了解了 EDP 之间定义明确的关系如何促进重构规划。

So far, this chapter has been a series of lessons on working with EDPs. You learned how to compose EDPs and other patterns into larger abstractions that have solid and workable definitions. You learned how to look for patterns that you already know of when reading about new patterns. We discussed isotopes, which allow design patterns and their implementations to be decoupled somewhat, allowing the encapsulation of design concepts and flexibility of expression in code. You even were introduced to the established libraries of refactoring approaches, saw how they work in concert with the EDPs, and saw how the well-defined relationships among the EDPs can facilitate refactoring planning.

我们涵盖了广泛的主题,但现在我们可以将它们总结成一些相当简单的图表和示意图。下图可以更好地了解 EDP 之间的关系。一些模式名称对您来说是新的。它们尚未详细讨论,但您将在本书后面的目录章节中找到它们。

We covered a wide range of topics, but now we can summarize them into some fairly simple charts and diagrams. The following figures provide a better sense of how the EDPs relate to one another. Some pattern names will be new to you. They haven’t been discussed in detail, but you’ll find them in the catalog chapters later in this book.

图 4.36 显示了其他 EDP 使用哪些 EDP,特别是方法调用 EDP 中涉及哪些核心 EDP。如您所见,有一些清晰的排列构成了概念性的星座。在某些情况下,这里的用法是隐式的,而不是显式的。例如,重定向不显式使用 Retrieve,但由于 Redirection 在两个对象之间运行,因此必须在某个时候参与 Retrieve,才能使一个对象可供另一个对象调用该方法。另一方面,Inheritance 显式地出现在使用子类化的 6 个 EDP 中。

Figure 4.36 shows which EDPs are used by other EDPs and particularly which of the core EDPs are involved in the method-call EDPs. As you can see, there are some clear arrangements that form conceptual constellations. In some cases, the uses here are implicit, not explicit. Redirection, for instance, doesn’t explicitly use Retrieve, but because Redirection operates between two objects, Retrieve must be involved at some point to make one object available to the other for calling a method on it. On the other hand, Inheritance explicitly appears in the six EDPs that use subclassing.

图像

图 4.36.EDP 和选定其他模式之间的隐式 used-by 关系。

Figure 4.36. Implicit used-by relationships among the EDPs and selected other patterns.

首先,请注意 Retrieve 在 digram 的顶部被大量使用。如您所料,使用 Retrieve 的 EDP 涉及两个对象。这些是不同对象方法调用 EDP。它们下面是四个相似对象的 EDP。

First, notice that Retrieve is used heavily in the top portion of the digram. The EDPs that use Retrieve, as you would expect, involve two objects. These are the dissimilar-object method-call EDPs. Below them are the four similar-object EDPs.

其次,继承在图的中间部分占主导地位。涉及 Inheritance 的方法调用 EDP 是其对象类型处于子类或同级类类型关系中的 EDP。

Second, Inheritance dominates the middle section of the diagram. Method-call EDPs involved with Inheritance are those whose object types are in a subclass or sibling class typing relationship.

另请注意,在方法调用 EDP 中,图中从左到右是对称的。左侧是不同方法之间的 EDP;右边的那些是类似方法之间的 EDP。

Note also that there is a symmetry in the diagram from left to right in the method-call EDPs. On the left are the EDPs between dissimilar methods; those on the right are EDPs between similar methods.

最后,核心和方法调用 EDP 框之外的模式是由两个或多个 EDP 组成的模式。例如,我们的老朋友 Fulfill Method 同时使用 Abstract InterfaceInheritance

Finally, the patterns outside the core and method-call EDP boxes are those composed of two or more EDPs. Our old friend Fulfill Method uses both Abstract Interface and Inheritance, for instance.

我在 Section 2.2.4 中向您展示了方法调用 EDP 的部分设计空间,现在在图 4.374.38 中以图形方式进一步充实了它。每个区域都显示设计空间的一半。在阅读 EDP 描述时,将其用作概念图将有助于您了解它们之间的关系。

I showed you part of the design space for method-call EDPs in Section 2.2.4, and it is now further fleshed out graphically in Figures 4.37 and 4.38. Each shows one half of the design space. Using this as a conceptual map as you read the EDP descriptions will help you see how they relate to one another.

图像

图 4.37.完整的方法调用 EDP 设计空间:不同方法。

Figure 4.37. The full method-call EDP design space: dissimilar method.

图像

图 4.38.完整的方法调用 EDP 设计空间:类似的方法。

Figure 4.38. The full method-call EDP design space: similar method.

这两个图对齐以匹配图 4.36 的左侧和右侧。图 4.37 显示了具有不同方法的所有方法调用 EDP;它是图 4.27 的应用,来自我们在 4.3 节中的讨论,到图 2.9 的左长边。通过转动这个长方体,我们可以用类似方法的 EDP 看到右侧的长边,如图 4.38 所示。图 4.38图 2.10图 2.9 右侧长边的应用。我们在 Section 2.2.4 中讨论了这些 EDP,当时我们考虑了采用类似方法的 EDP。这两个图只是同一个构造空间的左侧和右侧。它们显示了每个 EDP 在设计空间中的位置,该空间由我们开始的三个相似性轴定义:方法、对象和对象类型。每个 EDP 都通过沿这些轴的显式更改与其周围的 EDP 相关。

These two diagrams are aligned to match with the left and right sides of Figure 4.36. Figure 4.37 shows all method-call EDPs with dissimilar methods; it is the application of Figure 4.27, from our discussion in Section 4.3, onto the left long side of Figure 2.9. By turning this cuboid, we can look at the right long side, as in Figure 4.38, with the similar-method EDPs. Figure 4.38 is the application of Figure 2.10 onto the right long side of Figure 2.9. We discussed these EDPs in Section 2.2.4, when we considered EDPs with similar methods. These two diagrams are just the left and right sides of the same constructed space. They show where each EDP sits in the design space defined by the three similarity axes we started with: method, object, and object type. Each EDP is related to the EDPs surrounding it by explicit changes along those axes.

描述这些变化为我们提供了图 4.39 中的视图,以图 4.35 所示的内容为基础。这列出了将每个方法调用 EDP 转换为另一个方法调用 EDP 所需的更改,一次一个步骤,以提供上一节中介绍的原子重构。当系统的要求发生变化时,此图将非常有用。如果您知道需要在这个 EDP 领域的哪个位置,您就可以更好地预测最终的系统会是什么样子。左边的图对应于图 4.37,采用不同的方法,右边的图与图 4.38 的相似方法相匹配。如果 EDP 的方法相似性属性发生变化,则会从左移动到右,反之亦然。

Describing those changes gives us the view in Figure 4.39, by building on what we showed in Figure 4.35. This lists the change required to transform each method-call EDP into another one, a single step at a time, to provide the atomic refactorings introduced in the previous section. This diagram will be useful as the requirements of a system change. You can better predict what the resulting system will look like if you know where you will need to end up in this EDP space. The figure on the left corresponds to Figure 4.37, with the dissimilar methods, and the figure on the right matches up with Figure 4.38, with the similar methods. If the method similarity property of an EDP changes, you shift from the left to the right, or vice versa.

图像

图 4.39.方法调用 EDP 重构关系。

Figure 4.39. Method-call EDP refactoring relations.

例如,如果您实现了 Recursion 的一个实例,但需要在多个对象之间分解任务,则标记为不同对象的箭头将直接指向 Redirected Recursion。相反,如果您发现 Recursion 需要分解为不同的子任务,那么您将引入新的方法,并且您知道您将不再保留方法的相似性。在这种情况下,中间标记为 disdifferent method 的大箭头表示您应该向左滑动到该树上的相应位置,并且您最终会到达 Conglomeration。您可以使用的每个决策点都会引导您找到适当的相关概念。

For instance, if you implemented an instance of Recursion but need to break up the task among multiple objects, the arrow labeled different object points you directly to Redirected Recursion. If instead you find that the Recursion needs to be broken up into distinct subtasks, then you will be introducing new methods, and you know that you will no longer retain the method similarity. In that case, the large arrow in the middle labeled dissimilar method indicates you should slide left to the corresponding location on that tree, and you end up at Conglomeration. Each decision point that you have at your disposal will lead you to the proper, related concept.

当应用其他重构操作时,例如来自 Fowler [19] 和 Kerievsky [24] 的操作,您会发现这些操作会沿着这些路线移动您的设计元素。通过提前了解 EDP 将如何变化和转移,您可以更好地预测设计的最终结果并管理潜在问题。

When applying other refactoring actions, such as those from Fowler [19] and Kerievsky [24], you will find those actions moving your design elements along these routes. By knowing ahead of time how the EDPs will change and shift, you can better predict where your design will end up and manage potential problems.

这四个图表是理解各个 EDP 如何相互关联以及它们如何为设计更改提供可能性的概念核心。当您阅读以下目录时,您可能希望回顾这些数字以记住更大的图景。虽然每个设计模式都是一个独立的概念,但它们之间存在不同的关系,这些关系形成了一个更丰富的系统,以便将它们作为一个组进行处理和推理。

These four diagrams are the conceptual core for understanding how the individual EDPs relate to each other and how they provide possibilities for design alterations. As you read the following catalogs, you might want to refer back to these figures to keep the larger picture in mind. While each design pattern is a self-contained concept, distinct relationships exist between them that form a richer system for working with and reasoning about them as a group.

4.5. 为什么您可能想要阅读附录

4.5. Why You May Want to Read the Appendix

好吧,所以我有点作弊。我说过,你不必理解 EDP 背后的任何数学原理就能理解或使用 EDP。不过,在这一点上,我将提供一些基于形式主义的主张,你可以凭信心接受它们,也可以去阅读附录来说服自己它们的有效性。我希望你持怀疑态度并深入研究。

Okay, so I’m cheating a bit. I said that you didn’t have to understand any of the mathematics behind the EDPs to understand or use them. At this point, though, I’m going to offer a couple of claims based on the formalisms, and you can either take them on faith or you can go read the appendix to convince yourself of their validity. I hope you’re skeptical and dive in.

首先,作为一个整体,EDP 的正式主体提供了面向对象编程的可能设计的完整覆盖。请记住,这本书充其量只涉及现存可能的 EDP 的四分之一。如果不使用 EDP,就不可能编写面向对象的程序。是的,我知道这是一个大胆的说法。请阅读正式的花絮以获取完整的解释。提示:我们在第 2 章中确定了二元关系是我们可以形成的最小关系。给定我们可以在其之间形成关系的有限数量的事物,必须有有限数量的可能关系。考虑到我们只从对象、方法、字段和类型开始,您认为这个集合可以有多小?如果你的答案是 “大约基本设计模式的大小”,请给自己一个饼干。这正是 EDP 的意义所在:一个用人类术语描述面向对象编程中所有可能的最小关系的项目,以便我们可以更有效地共享、理解和使用它们。这本书是一个开始;我希望您能加入剩下的讨论。

First off, taken as a whole, the formal body of EDPs provides complete coverage of the possible designs of object-oriented programming. Remember, this book touches on only one-quarter, at best, of the possible EDPs that exist. It is simply impossible to write an object-oriented program without using the EDPs. Yes, I know that’s a bold claim. Please read the formal tidbits for the full explanation. Hint: we established in Chapter 2 that binary relationships are the smallest relationships we can form. Given a finite number of things between which we can form relationships, there must be a finite number of possible relationships. Considering we’re starting with only objects, methods, fields, and types, how small do you think this set can get? If your answer is “about the size of the Elemental Design Patterns,” give yourself a cookie. That’s precisely what the EDPs are: a project to describe all of the possible smallest relationships in object-oriented programming in human terms so that we can share, understand, and use them more effectively. This book is a start; I hope you join in for the remainder of the discussion.

其次,由 ρ 演算定义的形式主义基本上是 EDP 的来源。大多数设计模式是通过检查现有软件系统来查找重复解决方案而创建的。这种方法让我们可以描述我们以前做过的事情,这很关键,但 EDP 的独特之处在于它们可以从第一原则定义,而不仅仅是在事后定义。可以肯定的是,它们在现有系统中无处不在,而这些实例是我们用来辨别 EDP 的意图和编写规范的东西,就像任何其他设计模式一样。然而,由我们的上下文的正交轴定义的 EDP 设计空间提供了一个框架,用于填补我们没有意识到的空白,并建立 EDP 彼此之间的确切关系。

Second, the formalisms defined by the ρ-calculus are fundamentally where the EDPs come from. Most design patterns are created by finding repeating solutions by inspecting existing software systems. This approach lets us describe what we’ve done before, which is critical, but EDPs are unique in that they are definable from first principles, not just after the fact. To be sure, they are ubiquitously present in existing systems, and those instances are what we use to discern the intent of and write the specification for the EDP, just as with any other design pattern. The EDP design spaces defined by our orthogonal axes of context, however, provide a framework for filling in gaps we didn’t realize we had and for establishing exactly how the EDPs relate to one another.

这种差异类似于早期机械工程师分享他们通过反复试验设计的作品蓝图,而现代工程师能够在制造单个零件之前模拟完整的设计。前者是对所尝试内容的描述;后一种预测基于正式的推理和建模。EDP 使我们能够精确描述系统,并预测即使是最小的变化也会产生什么影响。此外,正如您在 Decorator 的重建中看到的那样,EDP 允许我们根据当前的需求处理不同粒度级别的大型抽象。EDP 还使我们能够描述发现的新设计模式,根据现有抽象来组合它们的定义。可能性真的没有尽头。

The difference is akin to that between early mechanical engineers sharing blueprints for creations that they designed through trial and error and modern engineers able to simulate a complete design before ever fabricating a single part. The former is a description of what was tried; the latter prediction is based on formal reasoning and modeling. EDPs enable us to describe systems precisely and predict what impact even the smallest changes will have. Furthermore, as you saw with the reconstruction of Decorator, EDPs let us work with large abstractions at varying levels of granularity based on the needs of the moment. EDPs also enable us to describe new design patterns as they are found, composing their definitions in terms of existing abstractions. There’s really no end to the possibilities.

这些功能意味着 EDP 允许我们使用软件系统做两件重要的事情。首先,我们可以定义和描述一个面向对象的软件,任何面向对象的软件,用旨在供人类使用和理解的术语来定义和描述,而不管使用哪种实现语言。我们可以捕获代码中的概念,并以适合人类推理的方式表达它们。我们展示了这些小概念可以迅速构建成更大、更有趣的概念,这些概念使用最佳实践(例如 GoF 模式)解决现实世界的问题。我们可以在多个粒度级别上记录我们的软件,并根据我们当下的需求选择要操作的抽象级别。其次,更诱人的是,因为这些概念都是形式化的,而且它们交互的方式也是形式化的,所以提取、检测、组合和描述的过程可以自动化。并非巧合,这就是 SPQR 旨在解决的问题。

These features mean that EDPs allow us to do two crucial things with software systems. First, we can define and describe a piece of object-oriented software, any piece of object-oriented software, in terms designed to be used and understood by humans, regardless of the implementation language. We can capture the concepts in the code and express them in a way that is appropriate for a human to reason about. We showed that these small concepts quickly build into larger, more interesting concepts that solve real-world problems using best practices, such as the GoF patterns. We can document our software at multiple levels of granularity and choose which level of abstraction to operate on, depending on our needs in the moment. Second, and more enticingly, because these concepts are each formalized, and the manner in which they can interact is likewise formalized, the process of extraction, detection, composition, and description can be automated. Not coincidentally, that is the problem that SPQR was designed to solve.

这仅仅是个开始。

That’s just the beginning.

4.6. 高级主题

4.6. Advanced Topics

本节介绍一些您可能会觉得有趣的高级概念。这些都不是理解 EDP 或阅读目录所必需的。如果您想浏览(但希望不要跳过)本节,请随时这样做。

This section introduces some advanced ideas that you might find intriguing. None of these are required for understanding the EDPs or reading the catalog. If you wish to skim (but hopefully not skip) this section, please feel free to do so.

4.6.1. 重点文档和培训

4.6.1. Focused Documentation and Training

SPQR 最初旨在帮助记录软件的现有实现,允许直接从实现中找到和记录设计模式文献描述的概念和抽象。目标是公开这些设计模式实例将为开发人员提供更好的系统心智模型,并且他们将有一条更容易的途径来生成我们都讨厌的讨厌的文档,并且更容易维护他们的代码库。然而,在此过程中的某个地方,很明显我们可能看错了东西。

SPQR was originally intended to help document existing implementations of software, letting the concepts and abstractions described by the design pattern literature be found and documented directly from the implementation. The goal was that exposing these design pattern instances would give developers a better mental model of their systems, and they would have an easier path to produce that pesky documentation that we all hate doing as well as an easier time maintaining their codebases. Somewhere along the way, however, it became obvious that maybe we were looking at the wrong thing.

设计模式是我们非常理解的概念。它们有据可查,它们很常见,并且是许多系统之间的共享基础设施。因此,如果您在软件中找到模式,那就太好了!您正在使用最佳实践来解决其他人已经解决的问题。祝贺!

Design patterns are those concepts that we understand well. They are well documented, they are common, and they are a shared infrastructure among many systems. So if you find patterns in your software, great! You are using best practices to solve problems that others have already solved. Congratulations!

然而,整个文档问题并没有得到解决。您仍然必须解释您如何实现所选的模式,以及它们如何与系统的其余部分交互。然而,除此之外,其他人解决的问题几乎可以肯定并不是您的软件的独特之处。它们只是您悬挂功能和巧妙代码的骨架,这些功能和巧妙的代码位使您的系统对您的客户更具吸引力,使其在市场上脱颖而出。这些独特的部分是您需要花时间在文档中解决的软件部分。您的同事和继承您代码的人会感谢您这样做。

The entire documentation problem isn’t solved, however. You still must explain how you implemented the patterns you chose and how they interact with the rest of the system. Beyond that, however, the problems that others have solved are almost certainly not what makes your software unique. They are simply the skeleton on which you hang the features and clever bits of code that make your system more compelling to your clients, those that make it stand out in the marketplace. Those unique sections are the portions of your software that you need to spend your time addressing in documentation. Your coworkers and those who inherit your code will thank you for doing so.

换句话说,系统中可由设计模式描述的部分不一定是需要记录的关键部分。仍有一个文档流程需要执行。但是,描述设计模式覆盖了系统的哪些部分可以告诉您不必花时间记录哪些内容。这是代码中您不必花费过多资源培训新员工的部分,您不需要花费数小时来考虑更改影响的部分,您不需要确保保持完整以满足客户的部分。

In other words, the portions of your system that are describable by design patterns are not necessarily the parts that are critical to document. There’s still a documentation process to be performed. Delineating what portions of your system are covered by design patterns, however, tells you what you don’t have to spend time documenting. It’s the portion of the code that you don’t have to spend excessive resources training new hires on, the portion you don’t need to take hours to consider the impact of change to, the portion that you don’t need to make sure stays precisely intact to satisfy your customers.

在高级设计模式中可描述的部分是那些作为您的基础设施的部分,正是因为它们是其他人已经破土动工来解决的部分。通过了解附加值的起点和终点,您可以将成本和工作量的范围缩小到对您的底线真正重要的部分。

The portions that are describable in high-level design patterns are those that are your infrastructure, precisely because they are portions that other people have already broken ground to solve. By understanding where your added value begins and ends, you can reduce the scope of your costs and effort to the parts that really matter to your bottom line.

当然,你会得到其余部分的世界级文档,因为其他人已经写好了。你只需要指出他们的工作。

And of course and you get world-class documentation for the remainder, because other people have already written it. You just have to point to their work.

4.6.2. 指标

4.6.2. Metrics

在任何工程学科中,定量指标都是提供反馈、确保预测结果的保真度以及提供洞察力的必要条件。软件工程也不例外,EDP 及其底层形式为衡量我们的工作提供了新的方法。

In any engineering discipline, quantitative metrics are a must to provide feedback, to ensure fidelity with predicted results, and to give insight. Software engineering is no different, and EDPs and their underlying formalisms make possible new ways to measure what we do.

表达的清晰度我们都经历过可读性不如我们想要的软件。如果幸运的话,我们可能体验过非常易于阅读的软件。为什么会这样?是什么让一些软件有意义,而另一个做同样事情的系统却是一场噩梦?

Clarity of Expression We all have experienced software that is less readable than we would like. If we’re lucky, we may have experienced software that is extremely easy to read. Why is this? What makes some software make sense, while another system that does the same thing is a nightmare to work on?

可读性不仅仅是选择正确的名称和使用正确的空格格式的问题。一个清楚地表达了它实现的概念的软件被认为是可读的。当负责理解它的程序员的认知负荷较低时,它被认为是清晰的。如果软件包含正确的概念,但它们隐藏在其他事物的纠结中,那么原始开发人员试图做的事情的意图和意义就很难找到。这是一个定性过程。当我们看到清晰的代码时,我们就知道它。

Readability is not just a matter of selecting the right names and using the correct whitespace formatting. A piece of software that clearly expresses the concepts that it implements is considered readable. When the cognitive load on the programmer tasked with understanding it is low, it is considered clear. If the software contains the proper concepts, but they are hidden in a tangle of other things, then the intent and the meaning of what the original developer was trying to do is difficult to find. This is a qualitative process. We know clear code when we see it.

那么,有什么方法可以确保概念的表达清晰并且实现尽可能可读呢?嗯,回想一下我们谈到了同位素。我们可以通过它与最小、最直接、最清晰的形式表达的匹配程度来衡量概念表达的相对清晰度。通常,这可以在特定 design pattern 的规范规范中找到。如果这种规范形式还不清楚,或者没有达到最简化的形式,那么它就不太可能通过社区的审查。设计模式的规范首先是清晰的。

So what’s one way to ensure that the expression of a concept is clear and that the implementation is as readable as possible? Well, recall that we talked about isotopes. We can measure the relative clarity of the expression of the concept by how closely it matches the smallest, most direct, clearest expression of form. Usually, this is found in the canonical specification for a particular design pattern. If this canonical form was not already clear or not at its most reduced form, it is unlikely that it could have passed muster with the community. The specification of a design pattern is intended to be, above all else, clear.

对于依赖链中的每个额外步骤或 EDP 实现中的每个不必要的部分,开发人员必须更加努力地工作才能看到端点之间的关系。因此,我们可以通过计算对概念表达不必要的但存在于实现中的同位素成分的数量来测量与最佳距离。基本上,我们计算实现单个 pattern 或概念实例所需的额外部分。

For each additional step in a chain of reliances or each unnecessary piece in the implementation of an EDP, the developer has to work harder to see the relationship between the end points. It follows that we can measure the distance from optimal by counting the number of isotopic components that are unnecessary to expression of the concept but are present in the implementation. Basically, we count the extra pieces required to fulfill an individual pattern or concept instance.

现在,这些额外的位对于系统中的其他概念来说可能是(也可能是)是必要的和有用的,但它们的存在掩盖了那个特定的概念表达。我们可以用它来衡量代码中概念的清晰度。我们可以测量概念的可读性,而且我们可以自动进行测量。此外,我们可以通过提供 EDP 到 EDP 级别的重构操作来提供澄清这些概念的指导。由人类工程师决定哪些概念最重要并且需要最清楚地表达,但至少这样他们有一种量化的方式来权衡选项并在重构计划之间进行选择。

Now, it may be (and probably is) true that those extra bits are necessary and useful for some other concept in the system, but their presence is obscuring that particular conceptual expression. We can use this as a measure of the clarity of a concept in the code. We can measure the conceptual readability, and we can do so automatically. Furthermore, we can provide guidance in clarifying these concepts by offering refactoring actions at the EDP-to-EDP level. It is up to the human engineers to decide which concepts are most important and need to be most clearly expressed, but at least this way they have a quantitative way to weigh the options and choose between refactoring plans.

抽象密度使用同位素和 EDP 为澄清意图表达提供指导,对于对代码中包含的特定概念进行精细精确测量非常有效,但这远非全部。代码质量的另一种衡量标准是在总体上查看它,并获得 50,000 英尺的大规模系统视图。抽象是我们将系统的块视为单个概念的一种方式,而不必始终处理所有细节。如果系统告诉你一段代码是 for 循环,这比尝试在机器级别逐步描述执行要快得多,也要干净得多。同样,每个设计模式或概念都是一个抽象,它以最小的带宽传达了大量信息。此外,正如我们所展示的,我们可以从小的抽象中组合出更大和更高级别的抽象,如果能找到合适的抽象来包含它,我们也可以一次描述系统的相当大的部分。

Abstraction Density Using isotopes and EDPs to provide guidance for clarifying expression of intent works well for fine-grained precise measurement of specific concepts embodied in the code, but it is far from the whole picture. Another measure of code quality is to look at it in the aggregate and get a large-scale, 50,000-foot view of the system. Abstractions are a way for us to think about chunks of a system as a single concept, instead of having to deal with all details at all times. If you are told that a section of code is a for loop, that is much faster and cleaner than trying to describe the execution at the machine level, step by step. Likewise, each design pattern or concept is an abstraction that conveys a tremendous amount of information with minimal bandwidth. Further, as we showed that we can compose larger and higher-level abstractions from small ones, we can also describe quite large sections of a system at once if an appropriate abstraction can be found to encompass it.

因此,如果我们可以用一个抽象而不是 100 个抽象来描述 1,000 行代码,那么我们同时需要管理的内容就会减少。我们可以将这数千行作为一个整体来处理,而不必担心内部结构。可以考虑、考虑、理解和重构系统的较大部分。

It therefore follows that if we can describe 1,000 lines of code with one abstraction instead of 100 abstractions, we have less to manage in our heads at the same time. We can work with those thousand lines as a single whole and not have to worry about the internals. Larger sections of the system can be considered, mulled over, understood, and possibly refactored.

总的来说,在其他条件相同的情况下,可以用一些高级抽象来描述的实现比必须用很多很多小的抽象来描述的实现更可取。我们称之为代码的抽象密度,以代码中存在的抽象数量来衡量,并根据其组合进行加权。换句话说,封装了六个较小概念的抽象被认为比六个小抽象加上另一个小抽象更重要和更可取。在每种情况下,总共有 7 个抽象,但在前一种情况下,只有一个抽象需要开发人员进行基本理解;在后一种情况下,必须在脑海中处理七条信息。

Overall, an implementation that can be described in a few high-level abstractions is preferable to one that must be described in many many small ones, all else being equal. We call this the abstraction density of the code, measured as the number of abstractions present in the code, weighted for their composition. In other words, an abstraction that encapsulates six smaller concepts is considered more important and preferable to the six small abstractions plus another small abstraction. In each case there are seven abstractions total, but in the former case, there’s only one that is required to be carried forward by a developer for basic understanding; in the latter situation, seven pieces of information must be mentally juggled.

上一段中的关键词是“在其他条件相同的情况下”。抽象可能由许多信息组成,但并非所有信息都将成为有意义抽象的一部分。我们可以通过测量代码中与上下文无关的依赖的数量来测量代码中的原始密度。请记住,每个依赖项都是代码中两个实体之间的连接;因此,每个依赖项都是开发人员必须跟踪的连接,但抽象减轻了这种负担。我们将这个原始数字称为代码的信息密度,表示为每行代码的依赖数。

The key phrase in the preceding paragraph is “all else being equal.” An abstraction may be formed from many pieces of information, but not all pieces of information will be part of a meaningful abstraction. We can measure the raw density in the code by measuring the number of context-free reliances it has. Remember, each reliance is a connection between two entities in the code; therefore each reliance is a connection that the developer must keep track of, but abstractions alleviate that burden. We call this raw number the information density of the code, presented as a number of reliances per line of code.

取两者的比率使我们能够确定每单位原始信息的抽象数量,即相对抽象密度 (RAD)。我们可以按方法、按类、按文件、按目录或按模块粒度执行此分析。它通过提醒项目经理最有可能出现问题的区域,帮助发现热点和潜在的问题区域。这并不是说这样的代码是错误的

Taking the ratio of the two enables us to determine the number of abstractions per unit of raw information, the relative abstraction density (RAD). We can perform this analysis on a per-method, per-class, per-file, per-directory, or per-module granularity. It helps expose hot spots and potential trouble areas by alerting a project manager to the areas that are most likely to be problematic moving forward. Which isn’t to say that such code is wrong.

RAD 越低,我们就越能预期代码的该区域难以处理。它可能高效、优化、行为完美且没有错误。我们只是认为当需求发生变化时,它不是很可维护。也许这正是工程师在那种情况下需要从该代码中获得的内容。但是,在潜在问题点咬您之前了解它们在哪里,可以让您和您的团队做出相应的计划。

The lower the RAD, the more we would expect that area of the code to be problematic to work with. It may be efficient, optimized, behave perfectly, and have no bugs. We just wouldn’t consider it to be very maintainable when requirements change. Perhaps that’s exactly what an engineer needs from that code in that case. Knowing where the potential problem spots are before they bite you, however, allows you and your team to plan accordingly.

另一方面,你可能有一个 RAD 非常高的模块,但你知道它充满了 bug。您可以决定继续在那里删除错误,因为高 RAD 让您确信代码更具可读性并且应该更易于使用。也许在这种情况下,培训开销足够低,您可以多找几个人来帮忙。同样,这是一个将正确的信息交到开发人员和项目负责人手中的问题,以便人类做出决策。

On the other hand, you may have a module with a very high RAD but that you know is riddled with bugs. You may decide to go ahead and stomp the bugs there, because the high RAD gives you confidence that the code is more readable and should be easier to work with. Perhaps in that case, the training overhead is low enough that you can bring on a couple more people to help out. Again, it’s a matter of getting the right information into the hands of the developers and project leads for decision making by humans.

4.6.3. 程序分析

4.6.3. Procedural Analysis

在整个讨论中,有一个基本假设,即所考虑的实现语言是面向对象的实现语言。毕竟,形式主义是面向对象的,UML 图是面向对象的,设计模式是面向对象的,对吧?嗯,不,不完全是。设计模式只是与其他任何概念一样的概念。请记住,其中一些语言(如 Create ObjectInheritance可以用过程语言非常保真地表示。毕竟,那是他们最初被发现的地方。面向对象编程的出现是因为它强制执行了过程语言中使用的最佳实践。

Throughout this discussion, there has been an underlying assumption that the implementation languages under consideration are object-oriented ones. After all, the formalisms are object oriented, UML diagrams are object oriented, and design patterns are object oriented, right? Well, no, not quite. Design patterns are just concepts like any other. Remember that some of them, such as Create Object and Inheritance, can be expressed in procedural languages with great fidelity. After all, that’s where they were first found. Object-oriented programming arose because it enforced best practices that were used in procedural languages.

事实证明,只要有一组正确的假设,就可以像面向对象代码一样轻松地分析过程代码的设计模式。还记得 2.2.2 节中关于如何在 C++ 中建模全局实体的讨论吗?在 C 语言中,一切都是全局的,但相同的技术也适用。在 IBM 研究院,我们能够成功地为具有数十万行代码的系统生成有意义的 UML 图,这些代码完全用 C 语言编写。创建一组足够细粒度的形式化概念,可以进行将过程惯用语与面向对象的假设相匹配所需的细粒度分析。用于可视化和考虑遗留代码的全新前景开始出现。

It turns out that, given the right set of assumptions, procedural code can be analyzed for design patterns just as easily as object-oriented code can be. Remember the discussion of how to model global entities in C++, back in Section 2.2.2? In C, everything is global, but the same technique applies. At IBM Research, we were able to successfully generate meaningful UML diagrams for systems with hundreds of thousands of lines of code, written exclusively in C. EDPs were the link. Creating a fine-grained enough set of formalized concepts allows for the fine-grained analysis required to match procedural idioms to object-oriented assumptions. Entirely new vistas for visualizing and considering legacy code start to appear.

4.7. 总结

4.7. Conclusion

我相信这个对 EDP 的快速介绍很有趣。我希望它也能带来启发。软件是我们最重要的现代便利设施之一,我们对它的理解非常脆弱。现在,我们更像是炼金术士而不是工程师,在不知道元素周期表的情况下,我们之间分享金块。EDP 以及产生它们的研究是为更严格的软件工程方法奠定基础的一种方式,这种方法也非常人性化。软件是人与机器、人与硅之间的协作,试图针对一个软件进行优化不可避免地会给软件软件带来问题。我建议我们让机器处理结构,我们处理概念,并使用以 EDP 为基础的设计模式作为两者之间的桥梁。

I trust this quick introduction to EDP has been interesting. I hope it has also been illuminating. Software is one of our most critical modern conveniences, one that we have an extremely tenuous grasp on. Right now we are more alchemists than engineers, sharing nuggets of gold among ourselves without knowledge of a periodic table. The EDPs, and the research that spawned them, are a way of creating a basis for a more rigorous approach to software engineering that is also very human. Software is a collaboration between man and machine, between human and silicon, and attempting to optimize it for one inevitably causes issues for the other. I propose that we let the machines handle the constructs, that we handle the concepts, and that we use design patterns with EDPs at the foundation as the bridge between the two.

你现在已经准备好阅读本书的其余部分,我希望你在阅读中发现它和我在写作中所做的一样大开眼界。如果我们作为一个社区和一个行业,能够开始从意图的角度来思考软件,并专注于构成我们设计的概念,而不是我们用来向编译器解释它们的构造,那么我们就可以专注于我们比任何机器都做得更好的事情。

You’re now ready to tackle the rest of this book, and I hope you find it as much of an eye-opener in the reading as I did in the writing. If we, as a community and an industry, can start thinking of software in terms of intent and focus on the concepts that form our designs instead of on the constructs that we use to explain them to a compiler, we can concentrate on what we do better than any machine.

我们设计。我们合作。我们沟通。我们解释。我们构建。

We design. We collaborate. We communicate. We explain. We build.

我们让现代世界运转起来。

We make the modern world run.

让我们更上一层楼。

Let’s take it to the next level.

5. EDP 目录

5. EDP Catalog

有了我们在前几章中构建的基础,您就可以开始研究本书其余部分介绍的 EDP 目录的第一部分了。介绍的前 16 种模式是 4 种基本的面向对象的编程模式,然后是 12 种方法调用 EDP。到目前为止,您已经了解了其中的 11 个,还有 5 个供您从包含的规格中自行学习。

With the foundation we built in the previous chapters, you’re ready to start investigating the first portion of the EDP Catalog presented in the remainder of this book. The first sixteen patterns presented are the four fundamental object-oriented programming patterns followed by the twelve method-call EDPs. You’ve been introduced to eleven of them so far, and there are five more for you to learn on your own from the included specifications.

用于编写设计模式的格式相当标准化。第一次流行的用法是在原始的 Gang of Four (GoF) 文本 [21] 中,我在这里遵循该形式。每个模式都写在几个部分,从 pattern 的名称开始,这样我们就有了一些规范的东西来引用它。Intent 部分说明了模式的用途。接下来,Motivation 部分提供了此模式所解决的问题的背景,然后 Applicability 部分说明了何时应该(或不应该)使用它。接下来,将提供一个示例结构,即 UML 图和扩展的 PINbox(在适当的情况下)。请记住,这种结构永远不能用作死记硬背的食谱,而只是作为一个例子。如果您需要更改实现,并且这样做时不改变您关心的关系,那就去做吧。概念仍然存在。

The format used to write up a design pattern is fairly standardized. The first popular use was in the original Gang of Four (GoF) text [21], and I follow that form here. Each pattern is written in several sections, starting with the pattern’s name, so that we have something canonical to refer to it by. The Intent section explains the pattern’s purpose. Next, the Motivation section provides background on the problem this pattern solves, and then the Applicability section explains when it should (or should not) be used. A sample structure is provided next as both a UML diagram and, where appropriate, an expanded PINbox. This structure is not ever to be used as a rote recipe, remember, but only as an example. If you need to change the implementation and do so without altering the relationships you’re concerned with, then do it. The concepts will still be there.

参与者和协作部分遵循并解决了这些概念。模式规范继续 后果 和 实现 部分。这里提供了示例代码,但同样,它仅作为指导原则。您的实现语言几乎肯定会极大地影响您选择表达设计模式的方式。在适当的情况下,关于这些概念如何用不同语言表达的讨论用于强调不同的实现看起来有多么不同,但每个实现仍然可以为将基础概念作为设计模式的一致描述提供基础。对相关模式的讨论完成了每个规范。

The Participants and Collaborations sections follow and address those concepts. The pattern specification continues with the Consequences and Implementation sections. Here, sample code is provided, but, again, it is to be taken only as a guideline. Your implementation language will almost certainly greatly impact how you choose to express a design pattern. Where appropriate, discussions of how these concepts are expressed in different languages are used to highlight just how varied different implementations may look, yet each still can provide the basis for consistent description of the underlying concepts as design patterns. A discussion of related patterns completes each specification.

EDP 分为三个主要类别:Object Elements、Type Relation 和 Method Invocation。

The EDPs are organized into three main categories: Object Elements, Type Relation, and Method Invocation.

Object Elements 是处理对象创建和定义的那些基本模式:Create ObjectRetrieveCreate Object 描述了我们何时、如何以及为何实例化对象,是什么使它们在过程系统中特别,以及为什么它们不仅仅是语法糖。Retrieve 概述了如何以及为何将对象用作封闭对象中的数据字段。

Object Elements are those elemental patterns that deal with the creation and definition of objects: Create Object and Retrieve. Create Object describes when, how, and why we instantiate objects, what makes them special over procedural systems, and why they are not merely syntactic sugar. Retrieve outlines how and why objects are used as data fields inside an enclosing object.

Type Relation 包含两个简单的模式:Inheritance 讨论了在面向对象的系统中有效地重用类型信息和方法体定义的主要方法,而 Abstract Interface 解决了需要将实现推迟到以后可能创建的类型定义的问题。

Type Relation contains two simple patterns: Inheritance provides a discussion of the primary method by which typing information and method body definitions are reused effectively in object-oriented systems, and Abstract Interface solves the problem of needing to defer implementations to type definitions that may be created at a later date.

最后一组 Method Invocation 包含本章中的最后 12 种模式,如第 2 章所述。

The last group, Method Invocation, contains the final 12 patterns in this chapter, as described in Chapter 2.

集合中的模式虽然小而精确,但很重要,因为它们在面向对象编程中无处不在,并且因为它们与大多数高级抽象设计模式不同,它们易于形式化。每个程序员每天都会使用这些模式,通常不需要有意识地工作。因为模式的一个目的是让人们意识到潜意识和条件反射地认为有用的事物,所以这些规范旨在培养这种意识并促进关于我们领域基本概念的讨论。

The patterns in the collection, while small and precise, are important because of their ubiquity in object-oriented programming and because they are amenable to formalization, unlike most higher abstraction design patterns. Every programmer uses these patterns on a daily basis, usually without conscious effort. Because one purpose of patterns is to bring awareness to that which is subconscious and reflexively seen as useful, these specifications are intended to foster that awareness and facilitate discussion about the basic concepts of our field.

此外,EDP 还可以作为更好地描述和讨论最佳实践设计模式的基础,这些模式在当前文献中可能不那么容易理解或令人难忘。这些 EDP 是理解的基石,使我们能够以重要的方式重塑关于设计模式的对话。

Additionally, the EDPs act as a foundation for better describing and discussing best-practice design patterns that may not be as readily understandable or memorable from the current literature. These EDPs are the building blocks of comprehension, empowering us to reshape our conversations about design patterns in a significant way.

创建对象

Create Object

对象创建

Object Creation

意图

确保新分配的数据结构在被系统的其余部分操作之前符合一组断言和前提条件,并且它们只能以预定义的方式进行操作。

To ensure that newly allocated data structures conform to a set of assertions and preconditions before they are operated on by the rest of the system, and that they can only be operated on in predefined ways.

也称为

实例

Instantiation

赋予动机

在计算机上运行的任何可执行文件中,数据必须具有要放置的存储空间以及用于该数据的函数或方法。没有行为的数据无法操作。没有数据的行为无法采取有意义的行动。任何程序员的基本才能都是知道如何以概念上有意义的方式将数据和功能联系在一起,并促进源代码的可维护性。对象实例化解决了两个问题:为数据建立默认有效状态,并在该数据上建立一组相关的已定义行为。

In any executable run on a computer, data must have storage space to be placed in and functions or methods to act on that data. Data without behavior is incapable of action. Behavior without data is incapable of meaningful action. A fundamental talent for any programmer is knowing how to tie together data and functionality in ways that make conceptual sense and facilitate maintainability of the source code. Object instantiation solves two issues: establishing both a default valid state for data and a set of related defined behaviors on that data.

在过程语言中,数据和行为是不同的,数据结构定义前者,函数负责后者。数据不仅仅是分配的内存:它是关于如何解释、设置和处理该内存空间中信息的一组假设。此外,这些假设还定义了一组对数据进行操作或操作有效的行为。

In procedural languages, data and behavior are distinct, with data structures defining the former and functions taking care of the latter. Data is more than just allocated memory: it is a set of assumptions about how to interpret, set, and work with the information in that memory space. In addition, those assumptions define a set of behaviors that are valid to operate on, or operate with, the data.

当需要数据时,它需要占用一个内存块,在许多语言中,该内存块被简单地分配并交还给程序员使用。此数据可能与类型相关联,也可能是无类型和原始数据。在大多数情况下,它是未初始化的,这意味着内存没有默认值。开发人员必须手动填写起始值,否则可能会在分配之前恰好位于内存位置的任何随机数据仍然存在。每个开发人员每次都必须完成此过程。当然,封装此初始化行为的合理方法是将其放入要调用的函数中,但这样做只会将需求提升一个级别,现在开发人员必须记住调用初始化函数。如果不这样做,则意味着以后对该数据进行操作的函数可能会被输入无效或不正确的值,但此要求通常不会自动或严格执行。

When data is needed, it requires a block of memory to occupy, which in many languages is simply allocated and handed back to the programmer for use. This data may be associated with a type, or it may be typeless and raw. In most cases, it is uninitialized, meaning that the memory has no default value. A developer must manually fill in the beginning values or risk that whatever random data happened to be in the memory location prior to allocation is still there. This process must be done every time, by every developer. Of course, a reasonable way of encapsulating this initialization behavior is to put it into a function to be called, but doing so only moves the requirement up a level, and now the developer must remember to call the initialization function. Failing to do so means that later functions that operate on that piece of data may be fed invalid or incorrect values, but this requirement is usually not automated or strictly enforced.

清单 5.1 用 C 语言演示了这个场景。假设你正在编写一个低级 GUI 库,用于为应用程序提供窗口。已提供初始化函数,但在调用该函数之前(如第 31 行和第 32 行所示),请求数据中的值基本上是随机的。我说“本质上”是因为 C 的某些实现确实为整数、浮点数等提供了默认值零,但这只是某些硬件上的一些实现。在其他系统上,这些值只是预先在内存中的任何内容。因为 Listing 5.1 是一个完整的程序,所以你可以在你的系统上运行它,看看你的特定系统是如何运作的。

Listing 5.1 demonstrates this scenario in C. Assume you’re writing a low-level GUI library for offering windows to an application. An initialization function has been supplied, but before it is called, as in lines 31 and 32, the values in the requested data are essentially random. I say “essentially” because some implementations of C do provide a default value of zero for integers, floats, and such, but this is only some implementations, on some hardware. On other systems, the values are simply whatever was in memory beforehand. Because Listing 5.1 is a complete program, you can run it on your system and see how your particular system behaves.

清单 5.1.未初始化的数据。

Listing 5.1. Uninitialized data.


   #include <stdio.h>

2 #include <string.h>

#include <stdlib.h>

4

struct WindowData {

6 int xPosition;

int yPosition 的

8 int 宽度;

int 高度;

10 个字符* 标题;

};

12

void

14 initializeWindowData( struct WindowData* wd ) {

wd->xPosition = 0;

16 wd->y位置 = 0;

wd->宽度 = 600;

18 wd->高度 = 800;

为字符串

20 分配足够的值(加上 1 以终止 NULL)

wd->title = (char*)malloc(

22 (strlen(“Defaulttitle”) + 1) * sizeofchar));

strcpy(wd->title, “默认标题”);

24 };



26 int

main(int argc, char** argv) {

28 struct WindowData wd;



30 这些通常打印随机数据

printf(“width:%d\n”, wd.width);

32 printf(“title:%s\n”, wd.title);



34 initializeWindowData(&wd);



36 这些 * 总是* 打印 0 和 “默认标题”

printf(“width:%d\n”, wd.width);

38 printf(“title:%s\n”, wd.title);

  };

   #include <stdio.h>

 2 #include <string.h>

   #include <stdlib.h>

 4

   struct WindowData {

 6     int xPosition;

       int yPosition;

 8     int width;

       int height;

10     char* title;

   };

12

   void

14 initializeWindowData( struct WindowData* wd ) {

       wd->xPosition = 0;

16     wd->yPosition = 0;

       wd->width = 600;

18     wd->height = 800;

       // Allocate enough for the string

20     // (Plus 1 for terminating NULL)

       wd->title = (char*)malloc(

22         (strlen("Defaulttitle") + 1) * sizeof(char));

       strcpy(wd->title, "Defaulttitle");

24 };



26 int

   main(int argc, char** argv) {

28     struct WindowData wd;



30     // These usually print random data

       printf("width:%d\n", wd.width);

32     printf("title:%s\n", wd.title);



34     initializeWindowData(&wd);



36     // These *always* print 0 and "Default title"

       printf("width:%d\n", wd.width);

38     printf("title:%s\n", wd.title);

  };


这导致了第二个问题,即以格式正确的方式处理格式正确的数据:不仅数据在开始时可能格式错误,而且对于函数可以对其进行操作的障碍也很少(如果有的话)。由于内存中存储的内容与实际存储内容的假设不匹配,不是为处理特定数据而设计的函数可能会错误地运行。此外,函数对数据的错误假设可能会以无效的方式更改基础值。以前格式正确的数据现在可能对数据类型无效。

This leads to the second issue involved with working with even well-formed data in well-formed ways: not only may the data be malformed in the beginning, but also there are few, if any, barriers to what functions may operate on it. Functions that were not designed to work on a particular piece of data may behave incorrectly due to a mismatch between assumptions of what is stored in the memory and what is actually there. In addition, a function’s incorrect assumptions about the data may alter the underlying values in an invalid way. What was previously correctly formed data may now be invalid for the data type.

通常,数据和一组相关函数作为库提供,但库仅将它们作为松散定义的组提供;没有任何东西以系统强制执行的方式主动将它们联系在一起。作为一项改进,可以将函数直接与它们在某些过程语言中操作的数据捆绑在一起,前提是您能够获得某种将函数合并到数据结构中的方法。

Often, data and a group of related functions are shipped as a library, but a library only provides them as a loosely defined group; nothing actively ties them together in a way that is enforced by the system. As an improvement, it is possible to bundle functions directly with the data they operate on in some procedural languages, provided you have access to some way of incorporating the functions into a data structure.

在 C 语言中,这可以通过函数指针来完成。每个要与结构关联的所需函数的函数指针直接放置在该结构中,然后与数据一起携带。1 这是指示哪些行为与特定数据相关联的好方法,但它并不妨碍开发人员简单地直接介入并摆弄数据。

In C, this can be done with function pointers. A function pointer for each desired function to be associated with a struct is placed directly within that struct and then is carried along with the data.1 This is a good way to indicate which behavior is associated with particular data, but it doesn’t prevent a developer from simply stepping in and fiddling with the data directly.

为了保护数据,我们需要封装,它将数据隐藏起来,同时提供一组定义明确且有限的方法来访问和操作数据。通过封装隐藏的数据称为私有数据。封装可防止开发人员直接操作数据,并将可能的行为限制为与数据捆绑在一起的集合。同样,可以使用过程语言执行数据封装,前提是您具有一些较低级别的访问权限,例如通过指针。此技术也称为指向实现的指针 (pimpl)、d 指针不透明指针Cheshire cat

To protect the data, we need encapsulation, which hides the data from view while providing a set of well-defined and limited ways to access and manipulate the data. Data hidden by encapsulation is said to be private. Encapsulation prevents a developer from directly manipulating the data and restricts the possible behaviors to the set bundled with the data. It is possible to perform data encapsulation in a procedural language, again, provided you have some lower-level access, such as through pointers. This technique is also called a pointer-to-implementation (pimpl), d-pointer, opaque pointer, or Cheshire cat.

将这两种技术结合起来,可以很好地保护数据,并为这些数据提供一组一致的可能行为。但是,只有某些过程语言可以进行行为捆绑和封装,并且它们非常混乱,容易出错,需要仔细考虑和实现。更糟糕的是,他们仍然依赖这些技术作为策略应用,而没有编译器或语言的自动执行。

Combining the two techniques results in a decent way of protecting data and providing a consistent set of possible behaviors on that data. The behavior bundling and encapsulation are possible in only some procedural languages, however, and they are quite messy, error prone, and require careful consideration and implementation. Worse yet, they still rely on these techniques being applied as a matter of policy, without automated enforcement by the compiler or language.

清单 5.2.固定默认值。

Listing 5.2. Fixed default values.


1 结构 WindowData {

int xPosition = 0;

3 int yPosition = 0;

int 宽度 = 600;

5 int 高度 = 800;

char title[] = “默认title”;

7 };

1 struct WindowData {

      int xPosition = 0;

3     int yPosition = 0;

      int width = 600;

5     int height = 800;

      char title[] = "Defaulttitle";

7 };


另一方面,对象是一个语言强制的、单一的、不可分割的单元,由概念上相关的数据和适用方法组成,如对象类型所定义。已确定作为对象类型一部分的方法彼此之间以及与数据之间具有有意义的关联。数据可以很容易地得到保护,我们可以确保这个单元在我们尝试对对象进行操作之前处于指定的连贯和定义明确的状态,并且我们可以确保只能对对象执行定义明确的操作。

An object, on the other hand, is a language-enforced, single, indivisible unit composed of data and applicable methods that are conceptually related, as defined by an object type. The methods chosen to be part of the object type have been determined to have a meaningful association with each other and with the data. The data can easily be protected, we can ensure that this unit is in a specified coherent and well-defined state before we attempt to operate on the object, and we can ensure that only well-defined operations can be performed on the object.

在某些过程语言中,虽然开发人员可以相当好地模拟对象以进行封装和捆绑行为,但他们无法绝对确保数据始终一致,或者对该数据状态的操作受到适当限制。几乎总有一些方法可以颠覆前面描述的技术来解决这些问题。

In some procedural languages, while a developer can emulate an object reasonably well for encapsulation and bundled behaviors, they cannot absolutely ensure that the data is always coherent or that the operations on the state of that data are properly restricted. There is almost always some way to subvert the techniques described previously to address these issues.

此外,至关重要的是,从业者在分配记录时无法保证记录的内容符合他们可能选择做出的任何特定断言。在前面的例子的基础上,我们可以为结构体的成员提供默认值,如 Listing 5.2 所示。WindowData

Also, and critically, the practitioner cannot guarantee at the time of allocation of the record that the record’s contents conform to any specific assertion they may choose to make. Building off of our earlier example, we could provide default values for the members of the WindowData struct, as in Listing 5.2.

现在我们不必调用 .每当我们定义该类型的新变量时,它都会预先填充适当的值。initializeWindowData()WindowData

Now we don’t have to call initializeWindowData(). Whenever we define a new variable of the WindowData type, it comes prefilled with the appropriate values.

如果这些默认值永远不需要更改,则此方法有效。更改这些值需要编辑和重新编译源代码。不幸的是,这通常不是我们所需要的。考虑一下在大多数现代 GUI 应用程序中请求新文档窗口时会发生什么情况。如果存在一个新窗口,则新窗口会略微显示在当前窗口的右侧和下方,并且通常具有“无标题”的标题,但如果具有该名称的窗口已存在且尚未重命名,则标题会附加一个计数器,例如“无标题 1”和“无标题 2”。如果可能,这种基本行为应由库提供,因此最好在请求新的 .WindowData

This approach works, provided these default values never need to change. Changing the values requires editing and recompiling the source code. Unfortunately, that’s often not what we need. Consider what happens when you request a new document window in most modern GUI applications. A new window appears slightly to the right and below your current one, if one exists, and usually has the title “Untitled,” but if one with that name already exists and has not been renamed, then the title appends a counter, such as “Untitled 1” and “Untitled 2.” This sort of basic behavior should be offered by the library if possible, so it would be nice to have this information set automatically for when you request a new WindowData.

清单 5.3.动态初始化

Listing 5.3. Dynamic initialization


 1 void

initializeWindowData( struct WindowData* wd ) {

3 wd->xPosition = currentWindow()->xPosition + 10;

wd->yPosition = 当前窗口()->yPosition + 10;

5 wd->width = currentWindow()->width;

wd->height = currentWindow()->height;

7 个字符 * currTitle = currentWindow()->title;

int counter = currentUntitledCounter();

9 if (counter == 0) {

设置为“无标题”: 8 个字符 + 1 NULL

11 wd->title =

char*)malloc(9 * sizeofchar));

13 strcpy(wd->title, “无题”);

} else {

15 为 ' ' 添加足够的字符,两位数

一次限制为 100 个无标题窗口

17 wd->title =

char*)malloc(12 * sizeofchar));

19 strcpy(wd->title, “无题”);

字符编号[3];

21 snprintf(num, 3, “%d”, 计数器 + 1);

strcat(wd->title, num);

23     }

   };

 1 void

   initializeWindowData( struct WindowData* wd ) {

 3     wd->xPosition = currentWindow()->xPosition + 10;

       wd->yPosition = currentWindow()->yPosition + 10;

 5     wd->width = currentWindow()->width;

       wd->height = currentWindow()->height;

 7     char * currTitle = currentWindow()->title;

       int counter = currentUntitledCounter();

 9     if (counter == 0) {

           // Set to "Untitled": 8 chars + 1 NULL

11         wd->title =

             (char*)malloc(9 * sizeof(char));

13         strcpy(wd->title, "Untitled");

       } else {

15         // Add enough chars for a ' ', and two digits

           // Limit of 100 Untitled windows at once

17         wd->title =

               (char*)malloc(12 * sizeof(char));

19         strcpy(wd->title, "Untitled");

           char num[3];

21         snprintf(num, 3, "%d", counter + 1);

           strcat(wd->title, num);

23     }

   };


但我们不能那样做。刚才描述的默认设置依赖于应用程序及其文档的现有状态,当我们编写或编译代码时,我们不可能知道这一点。当然,我们可以把这种动态信息收集和分配放在一个函数中,就像 Listing 5.3 一样,但是我们又回到要求开发者记住调用这个函数。使用默认的静态值并不能像我们希望的那样解决我们的问题。

But we can’t do that. The default settings just described rely on the existing state of the application and its documents, and we can’t possibly know that when we’re writing or compiling the code. We can, of course, put this sort of dynamic information gathering and assignment in a function, as in Listing 5.3, but then we’re right back to requiring the developer to remember to call this function. Using default static values doesn’t solve our problem as well as we’d like.

拥有初始化器函数将是一个有效的解决方案,但不是可执行的解决方案。实施依赖于策略、文档和工程师纪律,而这些都没有被证明最终是准确或可靠的。因此,工程师又回到了最初的问题,即无法保证任何给定的断言在新分配的数据上成立。恶意、粗心或懒惰的程序员可能会分配结构,然后无法调用正确的初始化过程,从而导致潜在的灾难性后果。

Having an initializer function would be an effective solution but not an enforceable one. Enforcement relies on policy, documentation, and engineer discipline, none of which have proven to be ultimately accurate or reliable. An engineer is therefore back to the original problem of not being able to guarantee that any given assertion holds true on the newly allocated data. A malicious, careless, or lazy programmer could allocate the structure, and then fail to call the proper initialization procedure, leading to potentially catastrophic consequences.

基于对象和类的系统提供了一种替代方案。当运行时环境分配对象时,它将以格式正确的方式进行初始化,具体取决于语言和环境。所有面向对象的环境都提供了一些类似的机制作为其实现的基本部分。此机制是实现者可以在该附加点创建函数 (通常称为初始值设定项或构造函数) ,以对对象执行适当的设置。通过这种方式,任何特定的断言(包括基于仅在创建对象时可用的信息的断言)都可以在数据可供系统其余部分使用之前对数据施加。阿拉伯数字

Object- and class-based systems provide an alternative. When an object is allocated by a runtime environment, it is initialized in a well-formed way that is dependent on the language and environment. All object-oriented environments provide some analogous mechanism as a fundamental part of their implementation. This mechanism is the attachment point at which the implementor can create a function (usually called the initializer or constructor) that performs the appropriate setup on the object. In this way any specific assertion, including those based on information only available at the time of object creation, can be imposed on the data before it is available for use by the rest of the system.2

对象的用户无法绕过此机制;它由 Language 和 Runtime Environment 强制执行。假设的恶意、粗心或懒惰的程序员被挫败,并避免了可能的错误。由于这种类型的未初始化错误通常极难追踪和识别,因此最好避免。

There is no way for a user of the object to bypass this mechanism; it is enforced by the language and runtime environment. The hypothetical malicious, careless, or lazy programmer is thwarted, and a possible error is avoided. Because this type of missed-initialization error is generally extremely difficult to track down and identify, avoidance is preferred.

这种在过程领域中最佳实践的执行在语言级别提供了一个强大的策略执行机制,并促进了对象超越了单纯的语法糖或便利性。

This enforcement of best practices in the procedural realm provides a strong policy-enforcement mechanism at the language level and promotes objects above mere syntactic sugar or convenience.

适用性

在以下情况下使用 Create Object

Use Create Object when:

• 您希望提供数据表示形式,并强制只能对任何特定实例执行某些操作。

• You wish to provide a data representation and enforce that only certain operations can be performed on any particular instance.

• 您希望提供数据表示的已分配实例,并确保在使用前满足一组前提条件。

• You wish to provide allocated instances of a data representation and ensure that a set of preconditions is met before use.

结构
图像
参与者

创建站点

CreationSite

请求创建类型为 的新对象 的对象。objectCreated

The object that requests the creation of a new object of type objectCreated.

对象已创建

objectCreated

要创建的对象。

The object to be created.

TypeToCreate

TypeToCreate

的类型 .objectCreated

The type of objectCreated.

合作

请求创建 object 的新实例 、 类型为 的实例。执行此操作的确切机制因语言而异,但通常包括发出对象类型本身的请求。实际上,请求是以对象类型作为参数提供给运行时环境的,但大多数语言的语法都会产生请求直接向对象类型发出的外观。正如精明的读者会注意到的那样,这是一个自我指涉的定义:在创建新对象之前,您必须有一个对象。启动整个链的初始对象在所用语言的运行时机制中发挥作用。例如,在 Java 中,用户告诉 JVM(Java 虚拟机)在哪个类中查找方法以启动整个系统。这是一个静态方法,因此它位于类的类对象中,但是,它仍然是一个类对象,并且满足我们的要求。CreationSiteobjectCreatedTypeToCreateCreationSitemain()

An instance of CreationSite requests that a new instance of object, objectCreated, of type TypeToCreate, be created. The exact mechanism for doing so varies among languages, but it generally consists of making the request “of” the object type itself. In reality, the request is given to the runtime environment, with the object type as an argument, but the syntax of most languages creates the appearance that the request is made directly to the object type. This is, as an astute reader will notice, a self-referential definition: you must have an object to be the CreationSite before making a new object. The initial object that kicks off this whole chain comes into play in the runtime mechanism of the language being used. For instance, in Java, the user tells the JVM (Java Virtual Machine) which class to find the main() method in to start the whole system. This is a static method, so it is in the class object for the class, but, still, it’s a class object and satisfies our requirement.

后果

大多数面向对象的语言不允许使用任何其他方法创建数据结构,但不需要定义开发人员提供的初始化例程。通常提供一个默认的初始化例程,它执行最少的设置。

Most object-oriented languages do not allow for the creation of data structures using any other method, yet do not require the definition of a developer-supplied initialization routine. A default initialization routine is generally supplied that performs a minimum of setup.

某些语言虽然是面向对象的,但允许创建非对象数据结构。C++ 和 Objective-C 是两个示例,它们都源自命令式 C 语言。Python、Perl 6 和其他语言具有允许此类行为的类似历史原因。

Some languages, although object oriented, allow the creation of nonobject data structures. C++ and Objective-C are two examples, both derived from the imperative C language. Python, Perl 6, and other languages have similar historical reasons for allowing such behavior.

创建对象后,只有原始类的开发人员提供的方法集才是对该对象的有效操作。有关如何更改此事态的示例,请参阅 Inheritance pattern。

Once an object is created, only the set of methods that were supplied by the developer of the original class are valid operations on that object. See the Inheritance pattern for an example of how to alter this state of affairs.

对象在其有用的存在结束时被丢弃。此释放具有类似的函数,即释放分配器或析构函数,该函数在最终释放数据的存储空间之前调用。此函数允许对对象的数据和资源施加任何后置条件。尽管为对象设置格式正确且明确的析构序列也是一种最佳实践,但事实证明,从计算的角度来看,析构只是为了方便。有了无限的资源可供我们支配,物品可以无限期地继续存在,不使用,而且我们永远不会再意外地使用旧的。只是因为我们的资源有限,所以在大多数系统中,销毁物体才被确定为必不可少的。然而,对象的构造将它们注入到工作环境中,以便它们可以用于计算,因此是一种要求,而不是一种便利。从概念上讲,某些对象类型可能依赖于对象的析构来强制执行某些抽象概念(集合的固定元素等),但这是类设计者的事情。

Objects are disposed of at the end of their useful existence. This deallocation has an analogous function, the deallocator, or destructor, that is called before the storage space of the data is finally released. This function allows any postconditions to be imposed on the data and resources of the object. Although it is also a best practice to have a well-formed and definite deconstruction sequence for objects, it turns out that, from a computational standpoint, deconstruction is a matter of convenience only. With infinite resources at our disposal, objects could continue to exist, unused, for an indefinite amount of time, and we would never accidentally use an old one again. It is only because we have finite resources that destruction of objects is determined essential in most systems. The construction of objects, however, injects them into the working environment so they can be used in computation and is therefore a requirement, not a convenience. Conceptually, some object types may rely on the destruction of objects to enforce certain abstract notions (fixed elements of a set, etc.), but it is a matter for the class designer.

实现

在 C++ 中:

In C++:

清单 5.4 是一个基本类,展示了封装、捆绑行为和默认状态。有关创建自己的最佳实践 C++ 类的优秀建议,请参阅 Scott Meyers 的 Effective C++ [28] 和 More Effective C++ [29]。

Listing 5.4 is a basic class showing encapsulation, bundled behaviors, and default state. For excellent advice on creating best practice C++ classes of your own, see Scott Meyers’s Effective C++ [28] and More Effective C++ [29].

清单 5.4.创建对象实现。

Listing 5.4. Create Object Implementation.


   ThreadCount {

2 public:

构造函数

4 ThreadCount() {

numThreads = getNumberOfRunningThreads();

6 };

ThreadCount(int newData) {

8 numThreads = newData;

};

10

个访问器

12 int getNumThreads() {

return numThreads;

14 };

ThreadCount setNumThreads(int newData) {

16 if (newData > 1) numThreads = newData;

};

18 私有:

int numThreads;

20 };



22 int

main(int argc, char** argv) {

24 实例化对象

ThreadCount mc;

26

已经设置好并准备好了

28 将打印出正在运行的线程

数 cout << mc.getPrivateData() << endl;

30

不会更改数据,值不好

32 mc.setPrivateData(-1);



34 会改变数据,值 ok
mc.setPrivateData
(100);

36

打印出 100

38 cout << mc.getPrivateData() << endl;

  };

   class ThreadCount {

 2 public:

       // Constructors

 4     ThreadCount() {

           numThreads = getNumberOfRunningThreads();

 6     };

       ThreadCount(int newData) {

 8         numThreads = newData;

       };

10

       // Accessors

12     int     getNumThreads() {

          return numThreads;

14     };

       ThreadCount setNumThreads(int newData) {

16         if (newData > 1) numThreads = newData; };

       };

18 private:

       int numThreads;

20 };



22 int

   main(int argc, char** argv) {

24     // Instantiate an object

       ThreadCount mc;

26

       // Already set up and ready to go

28     // Will print out the number of running threads

       cout << mc.getPrivateData() << endl;

30

       // Won't change the data, value isn't good

32     mc.setPrivateData(-1);



34     // Will change the data, value okay

       mc.setPrivateData(100);

36

       // Prints out 100

38     cout << mc.getPrivateData() << endl;

  };


相关模式

Create Object 是面向对象编程中的一个核心概念,因此随处可见。任何主要关注对象创建或分发的设计模式都基于此 EDP 构建。示例包括下一章中的 Retrieve NewRetrieve Shared 模式,以及 GoF 设计模式中的创建模式:Abstract FactoryBuilderFactory MethodPrototypeSingleton [21]。

Create Object is a core concept in object-oriented programming and as such is found everywhere. Any design pattern that concentrates mainly on the creation or distribution of objects builds off of this EDP. Examples include the Retrieve New and Retrieve Shared patterns in the next chapter and the Creational Patterns found in GoF’s Design Patterns: Abstract Factory, Builder, Factory Method, Prototype, and Singleton [21].

取回

Retrieve

对象结构

Object Structural

意图

在本地范围内使用来自另一个非本地源的对象,从而在本地范围对象和非本地源之间创建关系和连接。

To use an object from another nonlocal source in the local scope, thereby creating a relationship and a connection between the local scoping object and the nonlocal source.

赋予动机

对象是一种已建立且易于理解的机制,用于封装常见数据和方法以及实施策略,如创建对象中所示。但是,单个对象的用途极其有限。事实上,如果系统中只有一个对象,并且没有外部对象,则可以将其视为过程程序 — 所有数据和方法都是本地的,并且完全相互公开。非对象数据类型可以在任何支持使用函数对象和无方法类的面向对象的系统中伪造。因此,必须采用一种格式良好的方法来跨对象边界运输对象。在两种情况下,此方法是必需的,它们仅略有不同。最简单的情况是当外部对象具有正在访问的公开字段时。更复杂的情况是,当外部对象具有被调用的方法,并且该方法的返回值正在内部范围内使用时。

Objects are an established and well-understood mechanism for encapsulating common data and methods and enforcing policy, as shown in Create Object. Singular objects, however, are of extremely limited utility. In fact, if there were only one object in a system, and nothing external to it, it could be considered a procedural program—all data and methods are local and fully exposed to one another. Nonobject data types can be faked in any object-oriented system that supports the use of function objects and methodless classes. It is therefore critical that a well-formed methodology be put into place for transporting objects across object boundaries. There are two situations in which this methodology is necessary, and they differ only slightly. The simplest case is when an external object has an exposed field that is being accessed. The more complex case is when an external object has a method that is called, and the return value of that method is being used in the internal scope.

最简单的形式如清单 5.5 所示,使用 Java 作为示例语言。

The simplest form is shown in Listing 5.5, using Java as the example language.

清单 5.5.使用更新检索

Listing 5.5. Retrieve with an update.


 1 public class SoundSettings {

public int volume;

3 玩家专用偏移量调整音量

public int offset;

5 };



7 public class MusicPlayer {

public void setVolume( SoundSettings ss ) {

9 这是 Retrieve

this.settings.volume = ss.volume 的一个实例;

11 };

私有 SoundSettings 设置;

13 };

 1 public class SoundSettings {

       public int volume;

 3     // A player specific offset to adjust volume

       public int offset;

 5 };



 7 public class MusicPlayer {

       public void setVolume( SoundSettings ss ) {

 9         // This is an instance of Retrieve

           this.settings.volume = ss.volume;

11     };

       private SoundSettings settings;

13 };


如果外部数据的使用位于临时表达式的中间,如清单 5.6 中的 method,则可以考虑等效的 方法。adjustVolume1adjustVolume2

If the use of the external data is at the middle of a temporary expression, as in method adjustVolume1 of Listing 5.6, then an equivalent can be considered, as in method adjustVolume2.

清单 5.6.临时变量中检索。

Listing 5.6. Retrieve in a temporary variable.


 1 public class MusicPlayer {

public void adjustVolume1( SoundSettings ss ) {

3 这也是一个 Retrieve

this.settings.volume =

5 this.settings.offset + ss.volume;

};

7 public void adjustVolume2( SoundSettings ss ) {

这里 Retrieve 是显式

9 int tempVar = ss.volume;

this.settings.volume =

11 this.settings.offset + tempVar;

};

13 };

 1 public class MusicPlayer {

       public void adjustVolume1( SoundSettings ss ) {

 3         // This is also a Retrieve

           this.settings.volume =

 5             this.settings.offset + ss.volume;

       };

 7     public void adjustVolume2( SoundSettings ss ) {

           // Here the Retrieve is explicit

 9         int tempVar = ss.volume;

           this.settings.volume =

11             this.settings.offset + tempVar;

       };

13 };


适用性

在以下情况下使用 Retrieve

Use Retrieve when:

• 非本地对象提供对本地计算所需对象的访问,所需对象为:

• A nonlocal object provides access to an object that is required for local computation and the required object is either:

– 由方法调用的返回值提供,或者

– provided by a method call’s return value or

—— 由公开的 Field 对象提供。

– provided by an exposed field object.

结构

请注意,它可以是 method 或 public 字段。selected

Note that selected could be either a method or a public field.

图像
参与者

SourceAccess

SourceAccess

包含 selected 的对象 (或类) 类型。

The object (or class) type that contains selected.

SinkHolder (水槽支架)

SinkHolder

包含要为其赋予新值的项 target 的对象(或类)类型。

The object (or class) type that includes the item, target, to be given a new value.

检索

Retrieved

需要更新的值的类型和返回的值。

The type of the value to be updated and the value that is returned.

sink

被赋予新值的字段。

The field that is given a new value.

source

生成新值的方法或字段。

The method or field that produces the new value.

合作

这个简单的关系由两个对象和两个方法组成。区别因素是将返回值传输到本地对象空间,以及使用该检索到的对象对本地字段的更新。local 字段可以是定义的字段,也可以是表达式中间的临时值。

This simple relationship consists of two objects and two methods. The distinguishing factors are the transferral of a return value into the local object space and an update to a local field using that retrieved object. The local field may be a defined field, or it could be a temporary value in the middle of an expression.

后果

像这样将两个对象和/或类型捆绑在一起是家常便饭,但不应该不假思索地进行。任何时候以这种方式绑定两个对象时,您都会引入依赖关系:目标对象现在依赖于源对象。确保这是您需要做的。

Tying two objects and/or types like this is an everyday occurrence, but it should not be done without thought. Any time you bind two objects in this manner, you are introducing a dependency: the target object now relies on the source object. Make sure it’s what you need to do.

在具有动态类型的语言(如 Python、Ruby 或 JavaScript)中,SourceAccess、SinkHolder 和 Retrieved 的类型角色可能不会在所有情况下都显式显示。此外,开发人员可能会也可能不会提供强类型化。

In languages with dynamic typing, such as Python, Ruby, or JavaScript, the type roles of SourceAccess, SinkHolder, and Retrieved may not be explicitly shown in all cases. Also, a developer may or may not provide strong typing.

实现

在 C++ 中:

In C++:


 1 class SourceAccess {

public:

3 检索到的 source();

};



5 SinkHolder {

7 检索到的 sink;

SourceAccess srcobj;

9 public:

void operation() { sink = srcobj.source();

11 };

 1 class SourceAccess {

   public:

 3     Retrieved source();

   };

 5

   class SinkHolder {

 7     Retrieved sink;

       SourceAccess srcobj;

 9 public:

       void operation() { sink = srcobj.source(); }

11 };


在 Java 中:

In Java:


 1 public class SourceAccess {

public 检索到的 source();

3 };



5 public class SinkHolder {

private Retrieved sink;

7 私有 SourceAccess srcobj;

public void operation() {

9 sink = srcobj.source();

};

11 };

 1 public class SourceAccess {

       public Retrieved source();

 3 };



 5 public class SinkHolder {

       private Retrieved sink;

 7     private SourceAccess srcobj;

       public void operation() {

 9         sink = srcobj.source();

       };

11 };


在 Python 中(注意动态类型):

In Python (note dynamic typing):


1 class SourceAccess(object):

def source(self):

3 pass // 此处



返回值 5 class SinkHolder(object):

def void operation(self):

7 self.sink = self.srcobj.source()

1 class SourceAccess(object):

      def source(self):

3         pass  //  Return value here



5 class SinkHolder(object):

      def void operation(self):

7         self.sink = self.srcobj.source()


相关模式

Retrieve 是一种基本的 EDP,与涉及两个或多个对象的任何其他模式一起出现,其中这些对象在运行时组合在一起。请参阅第 6 章中的 Retrieve NewRetrieve Shared 模式,了解涉及对象所有权和实例化的此 EDP 的变体。

Retrieve is a fundamental EDP and is found with any other pattern that involves two or more objects, where those objects are brought together at runtime. See the Retrieve New and Retrieve Shared patterns in Chapter 6 for variations on this EDP where object ownership and instantiation are involved.

遗产

Inheritance

类型关系

Type Relation

意图

重用另一个类的接口、实现和行为,并添加或更改每个类。

To reuse another class’s interface, implementation, and behavior with additions to or alterations of each.

也称为

IsA,类型重用

IsA, Type Reuse

赋予动机

通常,现有类为生成新的类类型提供了一个很好的开始。该接口可能几乎正是你正在寻找的,现有方法可能几乎提供了你新类所需的内容,或者至少,现有类可能在概念上接近你希望实现的目标。

Often, an existing class provides an excellent start for producing a new class type. The interface may be almost exactly what you are looking for, the existing methods may provide almost what you need for your new class, or, at the very least, the existing class may be conceptually close to what you wish to accomplish.

在这种情况下,重用现有类而不是从头开始重写所有内容是有用且高效的。一种方法是将代码复制并粘贴到新类中。这种技术经常使用,但它有很多缺点。如果在原始代码中发现错误,您现在有两个地方来跟踪和维护修复。如果对代码的一个副本进行了增强,则必须明确告知处理另一个代码位置的任何人该代码存在。复制和粘贴在开始时似乎是一种快速简便的方法,但它不仅将代码副本捆绑在一起,还将开发团队捆绑在一起。此外,在许多情况下,您根本没有要复制的源代码,例如在使用供应商的开发 API 时。

In such cases, it is useful and efficient to reuse the existing class instead of rewriting everything from scratch. One way of doing so is by copying and pasting the code into a new class. This technique is done quite often, but it has many drawbacks. If a bug is found in the original code, you now have two places to track and maintain the fix. If an enhancement is made to one copy of the code, anyone working on the other code location must be explicitly told that it exists. Copy and paste seems like a quick and easy approach in the beginning, but it doesn’t just tie the copies of code together, it ties the development teams together. In addition, in many cases, you won’t have the source code to copy at all, such as when using a vendor’s development API.

实现此重用的更好方法是通过 Inheritance 模式。每种面向对象的语言都支持这种代码重用方法,它通常是此类语言的核心原语。

A better way to accomplish this reuse is through the Inheritance pattern. Every object-oriented language supports this approach of code reuse, and it is usually a core primitive of such a language.

在最基本的层面上,此模式提供超类基类子类派生类之间的关系。超类(我们称之为 )是系统中现有的类,它至少为方法和/或数据结构提供概念接口。第二个类可以定义为派生自 ;我们称之为 。该类继承了 的所有方法和字段的接口和实现,这为程序员开始 工作提供了一个起点。SuperclassSuperclassSubclassSubclassSuperclassSubclass

At its most basic level, this pattern offers a relationship between a superclass or base class, and a subclass or derived class. The superclass (let’s call it Superclass) is an existing class in the system, one that provides at a minimum an interface of concepts for methods and/or data structures. A second class can be defined as being derived from Superclass; let’s call it Subclass. The Subclass class inherits the interface and implementations of all methods and fields of Superclass, and this provides a starting point for a programmer to begin work on Subclass.

假设您正在编写一个库以在图形界面中显示形状,例如,这可能是游戏引擎的核心。每个形状都会有一些基本信息,无论它是哪种形状。它将在屏幕上有一个位置,它将有一个颜色,绘制其边界的线条将具有粗细,依此类推。您可以将此信息添加,并实现处理数据的方法,添加到表示形状的每个类中。或者,你可以使用 Inheritance,如 Listing 5.7 所示。该类为系统中的任何形状设置基本信息,并提供一组用于处理该数据的基本方法实现。Shape

Assume that you are writing a library to display shapes in a graphical interface, such as might be at the core of a game engine. Every shape is going to have some basic information, regardless of what kind of shape it is. It will have a position on the screen, it will have a color, the line that draws its border will have a thickness, and so on. You could add this information, and implement the methods to work with the data, to each and every class that represents a shape. Or, you could use Inheritance as in Listing 5.7. The Shape class sets up the basic information for any shape in the system and provides a basic set of method implementations for working with that data.

您可能会注意到,在类中,我们重新声明了 instance 方法 .这是 override(覆盖)的一个示例,并且是通常与 Inheritance 一起出现的行为。Overrided(覆盖)允许您通过提供方法的新定义来自定义基类中的某些方法。您可以使用它来更改现有方法的行为,方法是完全替换它或以新的方式对其进行扩充。清单 5.8 演示了使用 .我们想让两者的客户端仍然使用界面,因为正方形只是一种特殊的矩形,但现在我们必须确保边的大小相同。我们添加对数据的检查,然后从我们的超类中调用预先存在的方法。没有必要完全替换 implementation,因为我们只是将其包装在有效性检查中。3SquaresetWidth:andHeightSquareSquareRectanglesetWidth:andHeight

You may notice that in the Square class we re-declared the instance method setWidth:andHeight. This is an example of overriding and is a behavior that commonly goes along with Inheritance. Overriding lets you customize certain methods from a base class by providing a new definition of the method. You can use it to change the behavior of an existing method, either by completely replacing it or by augmenting it in new ways. Listing 5.8 demonstrates overriding with Square. We’d like to let clients of both Square and Rectangle still use the setWidth:andHeight interface, since a square is just a special kind of rectangle, but now we have to make sure the sides are the same size. We add a check on the data, and then call the preexisting method from our superclass. There’s no need to completely replace the implementation because we’re just wrapping it in a validity check.3

清单 5.7.Objective-C 中的基本继承示例。

Listing 5.7. Basic inheritance example in Objective-C.


 1 @interface 形状

{

3 int xPos;

int yPos;

5 int lineWidth;

颜色* 填充颜色;

7 }

- (无效) setPosWithX: (int) x andY: (int) y;

9 - (void) setColor: (颜色*) c;

- (无效) setLineWidth: (int) lw;

11 @end



13 @interface 圆 : 形状

{

15 int radius;

}

17 - (void) setRadius: (int) r;



@end 19

@interface 矩形 : 形状

21 {

int width;

23 int 高;

}

25 - (无效) setWidth: (int) w andHeight: (int) h;



@end 27

@interface 正方形 : 矩形

29 {

}

31 - (void) setWidth: (int)w andHeight: (int) h;

- (无效) setSize: (int) s;

33 @end

 1 @interface Shape

   {

 3     int xPos;

       int yPos;

 5     int lineWidth;

       Color* fillColor;

 7 }

   - (void) setPosWithX: (int) x andY: (int) y;

 9 - (void) setColor: (Color*) c;

   - (void) setLineWidth: (int) lw;

11 @end



13 @interface Circle : Shape

   {

15     int radius;

   }

17 - (void) setRadius: (int) r;

   @end

19

   @interface Rectangle : Shape

21 {

       int width;

23     int height;

   }

25 - (void) setWidth: (int) w andHeight: (int) h;

   @end

27

   @interface Square : Rectangle

29 {

   }

31 - (void) setWidth: (int)w andHeight: (int) h;

   - (void) setSize: (int) s;

33 @end


清单 5.8.覆盖实现。

Listing 5.8. Overriding an implementation.


 1 @implementation 正方形

- (void) setWidth: (int) w andHeight: (int) h

3 {

if (w == h) {

5 [super setWidth: w andHeight: h];

} else {

7 printf(“ERR:Width!=height\n”);

}

9 }

- (void) setSize: (int) s

11 {

[self setWidth: s andHeight: s];

13 }

@end

 1 @implementation Square

   - (void) setWidth: (int) w andHeight: (int) h

 3 {

      if (w == h) {

 5        [super setWidth: w andHeight: h];

      } else {

 7        printf("ERR:Width!=height\n");

      }

 9 }

   - (void) setSize: (int) s

11 {

      [self setWidth: s andHeight: s];

13 }

   @end


有时,您需要为依赖原始行为的代码保持原始行为的完整性,但您希望为新代码提供一个固定版本。为此,可以创建一个继承自原始类的新类,但会覆盖 broken 的方法并提供固定版本。然后,现有代码可以继续使用基类,并完成它可能正在解决的 bug,但新代码可以利用修复版本。由于旧代码经过检查和测试,因此也可以将其迁移到新版本。

Sometimes you need to keep intact the original behavior for code that relies on it, but you want to provide a fixed version moving forward for new code. You can do this by creating a new class that inherits from the original but overrides the broken method and provides a fixed version. Existing code can then continue to use the base class, complete with the bug that it may be working around, but new code can take advantage of the fixed version. As the old code is inspected and tested, it can be migrated to the new version as well.

例如,假设你有一段 Java 代码,如 Listing 5.9 所示。这是一个完全人为的例子,用于演示通常所说的 fencepost 错误。如果你有一个 100 英尺长的栅栏,每 10 英尺有一个栅栏柱,你需要多少个栅栏柱?如果你说 10 个,你就有好伙伴了。许多人忘记了在栅栏上的零标记处还有一个额外的栅栏柱。这是一个非常常见的错误,也称为 off-by-one 错误。的实现只返回请求的栅栏柱的高度。问题是,在 Java 中,heights 数组是从零开始的。这意味着第一个元素的索引为 0,第二个元素的索引为 1,依此类推。问题是大多数人不会以这种方式看待栅栏。假设客户端代码询问用户他或她想要哪个栅栏柱的高度,然后将该条目传递给栅栏,如下所示。客户端代码必须在发送之前通过减去 1 来调整 的值,否则结果将是帖子的高度用户期望的高度之后。客户端代码必须解决此问题。顺便说一句,很容易争辩说 off-by-by one 错误不是 bug,而是 Java 数组的工作方式。这是真的。这也是 C 和 C++ 数组的工作原理。错误不在代码中;它存在于类实现者和使用类的开发人员之间的意图沟通错误中。实现该类的开发人员在执行该类时具有合理的假设。使用该类的客户端也有合理的假设。它们只是不是同一组假设。fencepostHeightsfence.getHeightOfPost(p)p

For instance, assume you have a piece of Java code such as in Listing 5.9. This is a totally artificial example to demonstrate what is commonly called the fencepost error. If you have a fence that’s 100 feet long, with a fencepost every 10 feet, how many fenceposts are you going to need? If you said 10, you’re in good company. Many people forget that there’s an extra fencepost at the zero mark on the fence as well. It is a surprisingly common bug, also known as an off-by-one error. The implementation for fencepostHeights simply returns the height of the requested fencepost. The problem is, the array of heights is zero-based in Java. This means that the first element has an index of 0, the second element has an index of 1, and so on. The problem is that most people don’t think of fences in this way. Assume that client code asks the user which fencepost he or she wants the height of, and then passes the entry to a fence, as in fence.getHeightOfPost(p). The client code must adjust the value of p by subtracting 1 before sending it in, or the result will be the height of the post after the one the user expects. The client code must work around this problem. By the way, it’s easy to argue that an off-by-one error isn’t a bug, that it is how Java arrays work. That much is true. It is also how C and C++ arrays work. The bug isn’t in the code; it is in the miscommunication of intent between the class implementer and the developers who use the class. The developer who implemented the class did so with reasonable assumptions. The client using the class also has reasonable assumptions. They’re just not the same set of assumptions.

清单 5.9.实现假设不匹配。

Listing 5.9. Implementation assumption mismatch.


  public 类围栏 {

2 int[] fencepostHeights;

public int getHeightOfPost( int post ) {

4 return fencepostHeights[post];

};

6 };



8 ...

栅栏;

10 栅栏在某个时候

被填满......

12 扫描仪扫描仪 = 扫描仪 (System.in);

System.out.println(

14 “进入围栏帖子get高度of:”);

int p = scanner.nextInt () ();

16 System.out.format(“帖子#%dhas高度of:%d%n”,

p, fence.getHeightOfPost(p - 1));

18 错误^^^^解决方法

  public class Fence {

2     int[] fencepostHeights;

      public int getHeightOfPost( int post ) {

4         return fencepostHeights[post];

      };

6 };



8 ...

      Fence fence;

10    // Fence gets filled in at some point

      ...

12    Scanner scanner = new Scanner (System.in);

      System.out.println(

14        "Enterfenceposttogetheightof:");

      int p = scanner.nextInt ();

16    System.out.format("Post#%dhasheightof:%d%n",

          p, fence.getHeightOfPost(p - 1));

18        // Workaround for error    ^^^^


Listing 5.9 开始,当有人意识到不匹配并决定修复(Listing 5.10)时会发生什么?fencepostHeights

Starting with Listing 5.9, what happens when someone realizes the mismatch and decides to fix fencepostHeights (Listing 5.10)?

清单 5.10.显而易见的解决方案 — 但可能不可行。

Listing 5.10. Obvious fix—but likely not feasible.


  public 类围栏 {

2 int[] fencepostHeights;

public int getHeightOfPost( int post ) {

4 return fencepostHeights[post - 1];

off-by-one 的 Bug 修复 ^^^^

6     };

  };

  public class Fence {

2     int[] fencepostHeights;

      public int getHeightOfPost( int post ) {

4         return fencepostHeights[post - 1];

          // Bug fix for off-by-one   ^^^^

6     };

  };


清单 5.11.修复 bug,同时保留旧代码。

Listing 5.11. Fixing a bug while leaving old code in place.


1 public class MendedFence extends Fence {

public int getHeightOfPost( int post ) {

3 return fencepostHeights[post - 1];

};

5 };

1 public class MendedFence extends Fence {

      public int getHeightOfPost( int post ) {

3         return fencepostHeights[post - 1];

      };

5 };


这个明显、直接和简单的修复方法只是破坏了将发送到的值调整到 的每一段客户端代码。现在,客户端代码将使用 post 的高度到预期高度的左侧。这可能没有预期的那么有用。如果有大量客户,或者他们的日程安排不同(几乎总是如此),那么几乎不可能协调所有团队一次整合修复程序。在这些情况下,通常的结果是 bug 持续存在,并且必须将解决方法合并到每个客户端中。此解决方案很脆弱且容易出错。4getHeightOfPost

This obvious, straightforward, and simple fix just broke every piece of client code that was adjusting the value sent in to getHeightOfPost. Now the client code will be using the height of the post to the left of the expected one. This is probably not as helpful a fix as was intended. If there is a large number of clients, or if they are on different schedules—which is almost always the case—then it’s nearly impossible to coordinate all teams to incorporate the fix at once. The usual outcome in these cases is that the bug perpetuates, and the workaround must be incorporated into every client. This solution is fragile and prone to error.4

解决此问题的更好方法是提供可供新 Client 端使用的类的固定版本,并允许旧 Client 端按照自己的计划迁移到该类。修复结果显示在 Listing 5.11 中,基于 Listing 5.9 中的原始类。新开发的代码可以改用此版本。它更干净,不需要记住调整帖子编号。旧客户端代码可以继续使用旧代码,直到有时间移动到新类。一旦所有客户端代码都使用新类,它和旧代码就可以合并为一个类供所有人使用。请注意,当 的源代码不可用时,此修复也可以正常工作。当您使用其他开发人员的库时,这种情况很常见。FenceFenceMendedFenceFence

A better way to fix this is to provide a fixed version of the Fence class that can be used by new clients and to allow old clients to migrate to it on their own schedule. The fix is shown in Listing 5.11, based on the original Fence class in Listing 5.9. Newly developed code can use this version instead. It is cleaner and doesn’t need to remember to adjust the post number. Old client code can continue using the old code until it has time to move to the new MendedFence class. Once all client code is using the new class, it and the old one can be merged into one class for everyone. Note that this fix can also work when the source code for Fence isn’t available. This is common when you’re using a library from another developer.

适用性

在以下情况下使用继承

Use Inheritance when:

• 现有类提供的接口、实现和数据存储几乎是新类所需的,但不完全是。

• An existing class provides an interface, implementation, and data storage that is almost, but not quite, what is needed for a new class.

• 出于维护原因,从原始类中复制和粘贴代码要么是不可取的,要么是由于原始源代码不可用而无法复制和粘贴。

• Copying and pasting the code from the original class is either undesirable for maintenance reasons or not possible because the original source code is unavailable.

结构
图像
参与者

超类

Superclass

系统中用作生成其他类的基础的现有类。

An existing class in a system that is used as a basis for producing a further class.

亚纲

Subclass

第二个类,它的基本接口和实现依赖于第一个类。

The secondary class that relies on the first for its basic interface and implementation.

合作

该类创建一组基本的方法接口,其中包含任何附带的可选方法实现。 继承 的所有 interface 元素,默认情况下,还继承所有实现,它可以选择用新的实现覆盖这些元素。SuperclassSubclassSuperclass

The Superclass class creates a basic set of method interfaces with any accompanying optional method implementations. Subclass inherits all the interface elements of Superclass, and, by default, all of the implementations as well, which it may choose to override with new implementations.

后果

继承是一种强大的机制,它有一些有趣的限制和后果。一方面,由于面向对象编程理论的核心存在一些微妙之处,子类可能不会删除方法或数据字段。然而,可以非正式地说,继承在子类和超类之间创建了所谓的 IsA 关系。子类的字面意思是 “Is A” 超类的专用版本。接口构成了类定义,因此通过定义一个接口来说明超类是什么,然后在子类中转身并删除该接口的部分,这意味着子类不再是超类的全部。是的,这一切都相当存在主义,但那对你来说就是面向对象的理论。最好坚持更简单的概念:如果不能毫无保留地将子类描述超类的实例,则不要子类化。

Inheritance is a powerful mechanism that has some interesting limitations and consequences. For one thing, due to a bit of subtlety at the heart of object-oriented programming theory, a subclass may not remove a method or data field. It can be stated informally, however, that inheritance creates what is called an IsA relationship between the subclass and the superclass. The subclass literally “Is A” specialized version of the superclass. The interface is what constitutes the definition of what the class is, so saying what the superclass is by defining an interface, and then turning around and removing portions of that interface in a subclass, means that the subclass no longer is everything the superclass is. Yes, it’s all rather existential, but that’s object-oriented theory for you. Best to stick with the simpler concept: If the subclass cannot be described without reservation as an instance of the superclass, then do not subclass.

许多语言提供多重继承,允许一个子类具有多个超类。这有效地允许将子类依次描述为每个超类。例如,牛奶既是液体又是食品。如果一个类继承自 ,那么它可以由 的属性来描述,例如粘度和凝固点。如果也是从 继承的,那么您可以讨论它的营养成分或有效期。但是,在这种情况下需要考虑一些问题,并且许多语言拒绝多重继承,而支持更简单的单继承模型。MilkFluidFluidMilkFoodItem

Many languages offer multiple inheritance, allowing a subclass to have multiple superclasses. This effectively allows a subclass to be described as each of the superclasses in turn. For instance, milk is both a fluid and a foodstuff. If a class Milk were to inherit from Fluid, then it could be described by the properties of a Fluid, such as viscosity and freezing point. If Milk also inherited from FoodItem, then you could discuss its nutritional content or expiration date. There are issues to be considered in such situations, however, and many languages reject multiple inheritance in favor of the simpler single-inheritance model.

在某些语言中,子类可以通过将方法设为私有来隐藏方法,但这并不是通用的。在所有语言中,子类可以覆盖方法并简单地提供一个空的实现,从而在接口保持不变的情况下有效地擦除行为。

In some languages, a subclass may hide a method by making it private, but this is not universal. In all languages, a subclass may override a method and simply provide an empty implementation, effectively erasing the behavior while the interface remains the same.

重写似乎是浪费基类中的好代码,这在许多情况下都是如此。Extend Method 模式通过在使用方法时重写方法来解决此问题。

It may seem that overriding is a waste of good code in the base class, and this is true in many cases. The Extend Method pattern solves this problem by overriding a method while still using it.

在某些情况下,你可能不希望继承整个现有类及其接口,而只继承一小部分功能。如果原始源代码不可用,您可能对方法体的实现缺乏信心,当只需要该类的一小部分时,您可能不愿意承担将大型类集成到当前系统中的成本,或者您可能有各种其他原因而仅使用现有类的一部分。

In some cases, you may not wish to inherit an entire existing class and its interface but instead to inherit only small pieces of functionality. You may lack confidence in the implementations of the method bodies if the original source code is unavailable, you may be reluctant to absorb the cost of integrating a large class into the current system when only a small segment of that class is needed, or you may have a variety of other reasons for using only part of the existing class.

当您希望保留部分(但不是全部)接口时,请考虑使用 Redirection 模式。你想要重用的类的一个实例被 Create Object 放在新类中,并且你想要保留的接口部分被复制,如清单 5.12 所示。在这里,该类实际上删除了以前必须解决的方法。缺点是你必须为希望保留的每个方法实现包装器,一直回到继承层次结构。例如,清单 5.12 包装了 Redirection 实例中的方法。5 您实际上正在擦除原始类中未使用的方法,但当您这样做时,您无法利用静态类型语言中的多态性。动态类型语言,特别是那些允许在运行时确定方法可用性的语言,例如 Objective-C 和 JavaScript,允许您更松散地处理类型,在不处于子类关系中的类型之间具有有效的多态性。基于原型设计的语言(如 JavaScript、Self 和 Lua)几乎普遍提供动态类型,并允许临时多态性而不会受到惩罚。SquaresetWidth:andHeightShape

Consider using the Redirection pattern when you wish to retain some, but not all, of an interface. An instance of the class you wish to reuse is placed in the new class using Create Object, and the portion of the interface you want to retain is replicated, as in Listing 5.12. Here, the Square class has effectively removed the setWidth:andHeight method that had to be worked around before. The downside is that you have to implement wrappers for every method you wish to retain, all the way back up the inheritance hierarchy. For instance, Listing 5.12 wraps methods from Shape in Redirection instances.5 You are effectively erasing the unused methods of the original class, but you do not get to take advantage of polymorphism in statically typed languages when you do so. Dynamically typed languages, particularly those that allow for runtime determination of method availability, such as Objective-C and JavaScript, let you play with the typing a bit more loosely, having effective polymorphism among types not in a subclassing relationship. Languages based on prototyping, such as JavaScript, Self, and Lua, offer dynamic typing almost universally and allow ad hoc polymorphism without penalty.

清单 5.12.使用 Redirection 隐藏界面的一部分。

Listing 5.12. Using Redirection to hide part of an interface.


 1 @interface 正方形

{

3 矩形 rect;

}

5 - (无效) setPosWithX: (int) x andY: (int) y;

- (无效) setColor: (颜色) c;

7 - (无效) setLineWidth: (int) lw;

- (无效) setSize: (int) s;

9 @end



11 @implementation 正方形

- (void) setPosWithX: (int) x andY: (int) y

13 {

[rect setPosWithX: x andY: y];

15 }

- (void) setColor: (颜色) c

17 {

[rect setColor: c];

19 }

- (void) setLineWidth: (int) lw

21 {

[rect setLineWidth: lw];

23 }

- (void) setSize: (int) s

25 {

[rect setWidth: s andHeight: s];

27 }

@end

 1 @interface Square

   {

 3     Rectangle rect;

   }

 5 - (void) setPosWithX: (int) x andY: (int) y;

   - (void) setColor: (Color) c;

 7 - (void) setLineWidth: (int) lw;

   - (void) setSize: (int) s;

 9 @end



11 @implementation Square

   - (void) setPosWithX: (int) x andY: (int) y

13 {

       [rect setPosWithX: x andY: y];

15 }

   - (void) setColor: (Color) c

17 {

       [rect setColor: c];

19 }

   - (void) setLineWidth: (int) lw

21 {

       [rect setLineWidth: lw];

23 }

   - (void) setSize: (int) s

25 {

       [rect setWidth: s andHeight: s];

27 }

   @end


将这种情况与您希望重用类的实现和/或数据存储但对接口不满意并希望提供新接口的情况进行比较。在这种情况下,您可以在新类中使用对象实例,这与 Redirection 示例中所做的非常相似,但通过委派 EDP 访问它。现在,您正在擦除原始接口并提供一个新接口,同时仍然使用底层数据存储和行为实现。此功能在用作 Facade 的类中很常见,这些类在接口或 API 之间进行适配。

Compare this situation with one in which you want to reuse the implementation and/or data storage of a class but are unsatisfied with the interface and wish to provide a new one. In this situation, you can use an object instance in the new class, much like was done in the Redirection example but access it via the Delegation EDP. Now you are erasing the original interface and providing a new one while still using the underlying data storage and implementation of behaviors. This feature is common in classes that are to be used as facades, those that adapt between interfaces or APIs.

这些使用重定向委派的组合来解决继承限制的技术非常常见,以至于某些语言为该功能提供本机支持,例如使用 C# 的关键字。delegate

These techniques to work around limitations of Inheritance using a combination of Redirection and Delegation are so common that some languages provide native support for the feature, such as with C#’s delegate keyword.

实现

创建继承关系的机制因语言而异,但静态类型化语言几乎总是为此提供清晰的语法。动态类型化或基于原型的语言可能并不总是使这种关系明确。

The mechanism for creating an inheritance relationship varies from language to language, but statically typed languages almost always provide clear syntax for doing so. Dynamically typed or prototype-based languages may not always make the relationship explicit.

在 C++ 中:

In C++:


  超类 {

2 public:

Superclass( );

4 };



6 子类 : public 超类 {

public:

8 子类( );

  };

  class Superclass {

2 public:

      Superclass( );

4 };



6 class Subclass : public Superclass {

  public:

8     Subclass( );

  };


在 Python 中:

In Python:


1 超类(对象):

def __init__(self):

3 传递



5 子类(超类):

def __init__(self):

7 Superclass.__init__(self)

1 class Superclass(object):

      def __init__(self):

3         pass



5 class Subclass(Superclass):

      def __init__(self):

7         Superclass.__init__(self)


相关模式

继承无处不在。它是任何使用 Subtype 或 Sibling 对象类型相似性的 EDP 的核心组件。具体而言,请参见受信任的委派、委派受信任的重定向委派重定向、还原方法扩展方法。此外,Inheritance 还用于 Fulfill Method 模式。因为这些模式中的每一个都是许多其他组合模式的基础,所以继承是一个 EDP,您应该确保自己彻底理解。

Inheritance is ubiquitously used. It is a core component of any EDP that uses object type similarities of Subtype or Sibling. In particular, see Trusted Delegation, Deputized Delegation, Trusted Redirection, Deputized Redirection, Revert Method, and Extend Method. In addition, Inheritance is used in the Fulfill Method pattern. Because each of these patterns is fundamental to many other composed patterns, Inheritance is an EDP you should make sure you understand thoroughly.

另请参阅 委派重定向 有关如何使用这些模式来解决继承的一些限制的更多信息。

Also see Delegation and Redirection for further information on how to use those patterns to work around some limitations of Inheritance.

抽象界面

Abstract Interface

类型关系

Type Relation

意图

提供一个公共接口,用于在一系列对象类型中应用行为,但不提供实际操作的实现。在这种情况下,子类被迫提供自己的正确实现,因为不存在默认方法。

To provide a common interface for applying a behavior in a family of object types but without providing an implementation of the actual operation. In this scenario, subclasses are forced to provide a proper implementation of their own because no default method exists.

也称为

虚拟方法、多态性、延迟实现

Virtual Method, Polymorphism, Defer Implementation

赋予动机

通常,当我们有一个使用符合概念设计的 Inheritance 模式的类层次结构时,我们会遇到这样一种情况:我们根本无法在类层次结构的根上提供有意义的方法实现。我们知道我们在概念上想要做什么,但我们并不完全确定如何去做。

Often, when we have a hierarchy of classes using the Inheritance pattern that conforms to our conceptual design, we run into a situation where we simply cannot provide a meaningful method implementation at the root of the class hierarchy. We know what we want to do conceptually, but we are not entirely sure how to go about doing it.

假设我们正在对动物进行建模。粗略地说,动物都有一定的行为和需求。他们吃东西,他们衰老,他们(通常)会移动。它们吃什么取决于物种,但进食几乎总是某种摄入行为。有些动物可能实际上并不摄入食物,但它们是罕见和特殊的情况。动物在成熟时会经历多个阶段,例如妊娠期、幼年期和成年期。每个阶段的细节和时间因物种而异,但至少可以模拟一个广泛的默认衰老过程。

Assume that we are modeling animals. Animals all, roughly speaking, have certain behaviors and needs. They eat, they age, and they (usually) move. What they eat depends on the species, but eating is almost always an act of ingestion of some sort. Some animals may not actually ingest food, but they are rare and special cases. Animals pass through a number of phases as they mature, such as gestation, juvenile, and adult. The details and timing of each stage vary across species, but at least a broad default process of aging can be modeled.

然而,移动有点棘手。我们可以为动物提供一个要移动到的目的地位置,但实现此行为是一项更复杂的任务:鱼、鸟和陆生动物都有非常不同的运动模式和它们可以到达的位置类型。我们在这里没有可以真正建模的过于宽泛的行为,至少从一开始就需要大量特殊情况。无论我们选择如何实现一个方法,我们最终都会在大多数子类中或多或少地完全覆盖和替换实现。我们很可能会得到 Listing 5.13 中的东西。moveTo

Movement, however, is a bit trickier. We can give a destination location for the animal to move to, but implementing this behavior is a more complex task: fish, birds, and terrestrial animals all have very different modes of locomotion and kinds of locations that they can get to. There’s no overly broad behavior that we can really model here, at least not without requiring a lot of special cases immediately from the beginning. However we choose to implement a moveTo method, we’re going to end up overriding and replacing the implementation more or less completely in most of the subclasses. We’re likely going to end up with something such as in Listing 5.13.

这告诉我们,也许我们根本不应该提供 implementation,因为无论我们选择什么,它几乎肯定会是错误的。我们希望确保每个 ,无论哪个子类描述它,都可以被要求移动,但我们无法提出一个合理的默认移动方式,该默认值适用于所有甚至大多数动物。我们希望提供一个接口 — 一种调用行为的定义方式 — 但没有实现来定义行为本身。Animal

This tells us that perhaps we shouldn’t offer an implementation at all, because no matter what we choose, it’s almost certainly going to be wrong. We want to ensure that every Animal, no matter which subclass describes it, can be asked to move, but we can’t come up with a reasonable default for how to move that would apply to all or even most animals. We want to provide an interface—a defined way of invoking the behavior—but no implementation to define the behavior itself.

清单 5.13.动物几乎都会移动,但方式却截然不同。

Listing 5.13. Animals almost all move but in very different ways.


 1 public class Animal {

public void eatFood( FoodItem f ) {

3 this.ingest(f);

this.digest(f);

5 };

public void matureTo ( TimeDuration age ) {

7 if (age > 妊娠) {

this.beAJuvenile(age);

9 } else if (age > maturation) {

this.beAnAdult(age);

11 } else if (age > longevity) {

this.die();

13 }

};

15 public void moveTo( Location dest ) {

Um...在这里放什么?

17     };

   };

 1 public class Animal {

       public void eatFood( FoodItem f ) {

 3         this.ingest(f);

           this.digest(f);

 5     };

       public void matureTo ( TimeDuration age ) {

 7         if (age > gestation) {

               this.beAJuvenile(age);

 9         } else if (age > maturation) {

               this.beAnAdult(age);

11         } else if (age > longevity) {

               this.die();

13         }

       };

15     public void moveTo( Location dest ) {

           // Um... what to put here?

17     };

   };


回想一下 继承 中的讨论,类型中的行为是由在类型上定义的方法提供的。因此,为了在所有子类中均匀地提供行为,我们应该提供一个方法定义,但随后我们就卡住了,因为我们没有定义方法的实现。

Recall from the discussion in Inheritance that behaviors in types are provided by the methods that are defined on a type. Therefore, to provide the behavior evenly across all the subclasses, we should provide a method definition, but we’re then stuck because we have no implementation to define the method with.

如果我们像 Listing 5.13 那样提供了一个空的实现体,那么我们就提供了一个什么都不做的默认实现。如果一个子类无法覆盖这个方法,那么由该类建模的可怜的动物将根本无法移动。尽管有一些真正静止的动物,但它们是例外。因此,在这种情况下,我们还希望确保提醒子类的实现者定义适当的实现,即使我们无法定义。解决方案是为该方法创建一个 Abstract Interface

If we provide an empty implementation body as in Listing 5.13, then we’ve provided a default implementation that simply does nothing. If a subclass fails to override this method, then the poor animal being modeled by that class won’t be able to move at all. Although there are some truly stationary animals, they are the exceptions. Therefore, in cases such as this, we would also like to ensure that the implementors of subclasses are reminded to define a proper implementation, even though we can’t. The solution is to create an Abstract Interface for that method.

适用性

在以下情况下使用 Abstract Interface

Use Abstract Interface when:

• 方法的实现在类定义时未知,或者无法确定合理的默认实现。

• Implementation of a method either is not known at class definition time or no reasonable default implementation can be determined.

可以确定该方法的界面。

• The interface for that method can be determined.

• 您希望子类根据其特殊需求处理方法的功能。

• You expect subclasses to handle the functionality of the method according to their special needs.

结构
图像
参与者

abstractor

Abstractor

声明操作接口的类类型。

The class type that declares an interface for operation.

操作

operation

被抽象出的方法:不能给出方法定义。

The method being abstracted out: no method definition can be given.

合作

Abstractor 为所有子类都将实现的方法定义一个接口。(此处的 和 an an-yet unknown 子类之间发生了隐式协作。Abstractor

Abstractor defines an interface for a method that all subclasses will implement. (An implicit collaboration occurs between Abstractor and an as-yet unknown subclass here.)

后果

Create Object 模式允许我们实例化对象,而 Retrieve 模式则显示如何填写新创建的对象的字段。抽象接口 (Abstract Interface) 的不寻常之处在于它表示没有方法实现;它不是向我们展示如何填充方法,而是向我们展示如何将方法定义推迟到设计中的另一个点。有关解决方案的其余部分,请参阅相关的 Fulfill Method 模式。

The Create Object pattern lets us instantiate objects, and the Retrieve pattern shows how to fill in the fields of that newly created object. Abstract Interface is unusual in that it indicates the absence of a method implementation; instead of showing us how to fill in the method, it shows us how to defer the method definition until another point in the design. See the related Fulfill Method pattern for the rest of the solution.

在这种情况下,声明方法是为了定义满足我们概念需求的适当接口,但方法主体未定义。这并不意味着我们简单地定义一个空方法,一个什么都不做的方法;该方法根本没有定义。这是一个关键点,也是刚接触面向对象语言的程序员经常错过的一点。具体操作方式因语言而异,您可以从 Implementation 部分的示例中看到。

In this case, the method is declared in order to define the proper interface for our conceptual needs, yet the method body is left undefined. This does not mean that we simply define an empty method, one that does nothing; the method has no definition at all. This is a critical point, and one that is often missed by programmers new to object-oriented languages. How it is done varies from language to language, as you can see from the examples in the Implementation section.

不同的语言对这种模式的用法也不同。例如,C++ 断言,任何类类型,即使只有一个方法(即抽象接口)也是抽象类,因此不能直接实例化。只有通过 Fulfill Method 模式为抽象方法提供定义的子类才能使用 Create Object 实例化为对象。

Different languages have different uses of this pattern as well. C++, for instance, asserts that any class type with even a single method that is an Abstract Interface is an abstract class and therefore incapable of being instantiated directly. Only subclasses that provide a definition for the abstract method via the Fulfill Method pattern may be instantiated into objects using Create Object.

Java 更进一步。与 C++ 一样,一个类可以包含一个或多个抽象方法,从而使类成为抽象的。但是,如果它只包含抽象方法,则 Java 会提供接口构造。此处的术语 interface 是指收集许多抽象方法的类状实体,而不是单个方法的 interface。与抽象类一样,Java 接口无法实例化。与抽象类不同,抽象类可以同时具有定义方法和抽象方法,Java 接口根本不允许具有任何已定义的方法。类应至少有一个已定义的方法,并且接口仅由抽象方法组成。尽管 Java 只有类的单一继承,但它提供了接口的多重继承,以允许以非常细的粒度组合新的类接口。有关继承模型的更多信息,请参阅继承

Java takes it even further. As with C++, a class may contain one or more abstract methods, making the class abstract. If it contains nothing but abstract methods, however, Java offers the interface construct instead. The term interface here refers to a classlike entity collecting many abstract methods, not the interface of a single method. Like an abstract class, a Java interface cannot be instantiated. Unlike abstract classes, which can have both defined and abstract methods, a Java interface is not allowed to have any defined methods at all. Classes should have at least one defined method, and interfaces are composed of only abstract methods. Although Java has only single inheritance of classes, it offers multiple inheritance of interfaces to allow the composition of new class interfaces at a very fine granularity. See Inheritance for more information on inheritance models.

Python 仅在库的 2.6 版中添加了对抽象方法的支持,作为模块中的标准库添加,它代表抽象基类。Python 3.0 引入了一种稍微简单一些的表示法,您将在 Implementation 部分看到。与 Java 或 C++ 不同,Python 的方法允许默认实现,该实现可以由子类调用,通常使用 Extend Method EDP。但是,子类仍然必须重写该方法并提供自己的实现。abc

Python added support for abstract methods only in version 2.6 of the library as a standard library addition in the abc module, which stands for abstract base class. Python 3.0 introduced a slightly more straightforward notation, which you will see in the Implementation section. Unlike Java or C++, Python’s approach allows for a default implementation, which can be called by subclasses, most often using the Extend Method EDP. Subclasses, however, still must override the method and provide their own implementation.

实现

在 C++ 中,该方法被设置为等于零,称为纯虚方法

In C++ the method is set equal to zero and called a pure virtual method:


   AbstractOperations {

2 public:

virtual void operation() = 0;

4 };



6 DefinedOperations :

public AbstractOperations {

8 public:

void operation();

10 };



12 void

DefinedOperations::operation() {

14 执行适当的工作

   };

   class AbstractOperations {

 2 public:

       virtual void operation() = 0;

 4 };



 6 class DefinedOperations :

       public AbstractOperations {

 8 public:

       void operation();

10 };



12 void

   DefinedOperations::operation() {

14     // Perform the appropriate work

   };


在 Java 中,该方法包含在接口中或位于抽象类中:

In Java the method is included in an interface or is in an abstract class:


 1 个公共接口 AbstractOperations {

public void operation();

3 };



5 public abstract class SemiDefinedOperations {

public abstract void operation2();

7 public void operation3() {};

};



9 public class DefinedOperations

11 扩展 SemiDefinedOperations

implements AbstractOperations {

13 public void operation() {

执行适当的工作

15 }

public void operation2() {

17 执行适当的工作

}

19 };

 1 public interface AbstractOperations {

       public void operation();

 3 };



 5 public abstract class SemiDefinedOperations {

       public abstract void operation2();

 7     public void operation3() {};

   };

 9

   public class DefinedOperations

11     extends SemiDefinedOperations

       implements AbstractOperations {

13     public void operation() {

           // Perform the appropriate work

15     }

       public void operation2() {

17         // Perform the appropriate work

       }

19 };


在 Python 3.x 中:

In Python 3.x:


1 class AbstractOperations(metaclass=ABCMeta):

@abstractmethod

3 def operation(self, ...):

// 允许

默认实现 5 return



7 class DefinedOperations(AbstractOperations):

defoperation(self, ...):

9 // 执行适当的工作

传递

1 class AbstractOperations(metaclass=ABCMeta):

      @abstractmethod

3     def operation(self, ...):

          // Default implementation allowed

5         return



7 class DefinedOperations(AbstractOperations):

      def operation(self, ...):

9         // Perform the appropriate work

          pass


相关模式

继承显然将与此 EDP 结合使用,以设置子类,该子类将为 Abstract Interface 中使用的方法提供实现。请参阅第 6 章中的 Fulfill Method 模式,了解如何执行此操作的完整故事。

Inheritance will obviously be used in conjunction with this EDP to set up the subclass that will provide an implementation for the method used in Abstract Interface. See the Fulfill Method pattern in Chapter 6 for the complete story on how to go about doing so.

代表团

Delegation

对象行为

Object Behavioral

意图

将当前工作的一部分分割或委托给另一个对象中的另一个方法。

To parcel out, or delegate, a portion of the current work to another method in another object.

也称为

消息传递、方法调用、调用、执行程序

Messaging, Method Invocation, Calls, The Executive

赋予动机

在使用对象的过程中,经常会出现 “some other object” 可以提供我们想要的功能的情况。委托体现了从一个对象到另一个对象的方法调用的最通用形式,允许一个对象向另一个对象发送消息,以执行一些工作。因此,接收对象可能会也可能不会发回数据。

In the course of working with objects, the situation often arises that “some other object” can provide a piece of functionality we want to have. Delegation embodies the most general form of a method call from one object to another, allowing one object to send a message to another, to perform some bit of work. The receiving object may or may not send back data as a result.

作为一个真实的例子,考虑一下公司的运作方式。首席执行官的目标是成功经营一家公司。她指派下属处理它的不同部分,很少有任何两个下属负责同一部分的操作。例如,财务副总裁确保财务报告符合政府标准,首席技术官确保满足公司的技术需求,等等。每项工作都是独立的,与其他工作以及 CEO 的职责有很大不同,但公司作为一个整体的成功取决于其所有部分的协同作用。清单 5.14 展示了一些对这种方法进行建模的代码。

As a real-world example, consider how a corporation works. The CEO’s goal is to successfully run a company. She assigns subordinates to handle different parts of it, and rarely are any two subordinates in charge of the same part of the operation. The vice president of finance, for instance, ensures that the financial reporting meets government standards, the CTO makes sure the technology needs of the company are addressed, and so on. Each job is discrete and differs greatly from the others as well as from the responsibilities of the CEO, but the success of the company as a whole relies on the synergy of all of its parts. Listing 5.14 shows a bit of code that models this approach.

清单 5.14.首席执行官委派责任。

Listing 5.14. CEO delegates out responsibilities.


   public 类首席执行官 {

2 FinanceExec vpFinance;

ResearchExec 副总裁研究;

4 TechnologyExec 首席技术官;

public void runCompany () {

6 vpFinance.ensureFinancialCompliance();

vpResearch runResearchDivision();

8 cto.manageTechnology();

};

10 };

   public class CEO {

 2     FinanceExec     vpFinance;

       ResearchExec    vpResearch;

 4     TechnologyExec  cto;

       public void runCompany () {

 6         vpFinance.ensureFinancialCompliance();

           vpResearch runResearchDivision();

 8         cto.manageTechnology();

       };

10 };


这里需要说明的一个有趣的点是,Listing 5.14 中的例程是同步方法调用的一个例子。在此 Java 代码中,调用时,要求财务副总裁管理财务,并且只有在完成报告时,才会要求研究副总裁开始其任务。任务由调用任务同步。首先是一个,然后是另一个,执行按特定顺序完成。runCompany

One interesting point to make here is that the routine in Listing 5.14 is an example of synchronous method calling. In this Java code, when runCompany is invoked, the vice president of finance is asked to manage the finances, and only when he is finished reporting is the vice president of research asked to start his task. The tasks are synchronized by the calling task. First one, then the other, and execution is done in a specific order.

当然,在现实生活中,这不是 CEO 执行工作的方式。她要求所有高管都去做他们的工作,同时做这些工作。然后,当每个人完成后,他或她会按照自己的时间表进行报告。这是异步执行的一个示例。代表们被告知去做他们自己的事情,同时。

In real life, of course, this isn’t how the CEO performs her job. She asks all the executives to go do their jobs and to do them at the same time. Then, when each is done, he or she reports back on his or her own schedule. This is an example of asynchronous execution. The delegates are told to go off and do their own thing, in parallel.

在大多数面向对象的语言中,这种异步或并行处理并不是一个简单的功能,尽管有些语言确实提供了线程库(如 Java 的 API)来满足这一需求。其他语言,尤其是 Go 和 Erlang 等函数式语言,通过异步调用对并发和并行功能提供本机支持,但正如您所料,它们的语法完全不同。无论它被称为异步调用、并发编程还是并行编程,它都是一门超出本书范围的整个学科。但请注意,本文中描述的每个 EDP 都可用作异步调用。FutureTask

Such asynchronous, or parallel, processing is not a simple feature to perform in most object-oriented languages, although some do offer threading libraries, such as Java’s FutureTask API, to fulfill this need. Other languages, particularly functional languages such as Go and Erlang, have native support for concurrency and parallel features via asynchronous calls, but their syntax is, as you might expect, radically different. Whether it is referred to as asynchronous calling, concurrent programming, or parallel programming, it is an entire discipline beyond the scope of this book. Note, however, that every EDP described in this text is applicable as an asynchronous call.

委派是设计和功能中足够常见的组件,因此 C# 提供了关键字。此语言功能将函数封装在对象中,以便可以像常规对象一样传递函数。然后,此委托对象将变得像常规方法一样可调用。封闭对象基本上是不可见的。6 这里的重点是,此功能不进行断言,并且除了与参数和返回值的预定义类型集匹配之外,对被包装的方法没有任何限制。被调用的委托对象显然与调用对象不同,对象类型彼此无关,并且被包装的方法可能被命名,也可能不被命名,就像执行调用的方法一样。这确实是最普遍的情况,也是 Delegation(委派)的一个示例。delegate

Delegation is a common enough component of design and functionality that C# offers the delegate keyword. This language feature encapsulates a function in an object such that it can be passed around as if it were a regular object. This delegate object then becomes callable as a regular method would be. The enclosing object is essentially invisible.6 The point here is that this feature makes no assertions and imposes no restrictions on the method being wrapped other than that it match a predefined set of types for the arguments and return value. The delegate object being called upon is obviously dissimilar from the calling object, the object types have nothing to do with one another, and the method being wrapped may or may not be named anything like the method doing the calling. This truly is the most general possible case and an example of Delegation.

适用性

在以下情况下使用 Delegation

Use Delegation when:

• 另一个对象可以执行当前方法主体希望完成的某些工作。

• Another object can perform some work that your current method body wishes to have done.

• 另一个对象不需要访问当前对象中的私有数据即可完成任务。

• The other object does not need access to the private data in the current object to complete the task.

• 两个对象之间没有已知的相关类型关系。

• There is no known relevant type relationship between the two objects.

结构
图像
参与者

委托人

Delegator

将消息发送到 的对象类型。Delegate

The object type sending the message to the Delegate.

操作

operation

发送消息时当前正在执行的方法 - operation2 方法调用的调用点。Delegator

The method within the Delegator that is currently being executed when the message is sent—the point of invocation of the operation2 method call.

委托

Delegate

接收消息的对象类型,以及要调用的相应方法。

The object type receiving the message, with an appropriate method to be invoked.

操作 2

operation2

从调用站点调用的方法。

The method being invoked from the call site.

合作

这是一个简单的二元关系:一种方法调用另一种方法,就像在程序系统中一样,并且具有相同的注意事项和要求。但是,由于我们在面向对象的领域中工作,因此我们还有一些额外的需求。我们要求被调用的对象在调用时可见,无论是通过本地范围的对象变量,还是通过 Retrieve 访问。此外,显然,被调用的方法必须对其他对象可见。

This is a simple binary relationship: one method calls another, just as in procedural systems, and with the same sorts of caveats and requirements. Because we are working in an object-oriented realm, however, we have a couple of additional needs. We require the object being called to be visible at the point of invocation, whether by being a locally scoped object variable, or by access through Retrieve. Also, obviously, the method being called must be visible to other objects.

后果

任意两个对象之间的所有操作都可以描述为 Delegation 模式的一个实例,但能够描述关系的更多属性要有用得多。请参阅其余的方法调用 EDP,了解更适合特定任务和需求的委派改进。但是,方法调用的这种通用形式在某些高级抽象(如 Bridge 或对象变体形式的 Adapter)中是一个关键概念,其中的重点是在两个不相关的接口之间创建格式良好且有效的转换。如果接口以有条不紊的方式相关,那么这些模式不一定是适合使用的模式。因此,接口和类型之间缺乏关系是一个必需的特征,而 Delegation 满足了这一需求。

All operations between any two objects can be described as an instance of the Delegation pattern, but being able to describe further attributes of the relationship is much more useful. See the remaining Method Invocation EDPs for refinements of Delegation that are better suited to specific tasks and needs. This generalized form of a method call, however, is a crucial concept in some of the higher abstractions such as Bridge, or Adapter in its object-variant form, where the point is to create a well-formed and effective translation between two interfaces that are otherwise unrelated. If the interfaces are related in a methodical manner, then these patterns are not necessarily the right ones to use. The lack of relation between the interfaces and types is a required trait, therefore, and Delegation fulfills that need.

实现

Delegation 是面向对象编程中最通用和最基本的方法调用样式,它描述了两个对象如何相互通信,作为消息的发送方和接收方,执行工作并返回值。

The most generalized and basic style of method invocation in object-oriented programming, Delegation describes how two objects communicate with each other, as the sender and receiver of messages, performing work and returning values.

在 C++ 中:

In C++:


   委托人 {

2 public:

受托人目标;

4 void operation() {

工作可能在...

6 target.operation2();

工作可能在...

8 };

};

10

Delegatee {

12 public:

void operation2();

14 };

   class Delegator {

 2 public:

       Delegatee   target;

 4     void operation() {

           // Work may be done before...

 6         target.operation2();

           // Work may be done after...

 8     };

   };

10

   class Delegatee {

12 public:

       void operation2();

14 };


相关模式

委派在面向对象的编程中无处不在。如果没有其他方法调用 EDP 适用,则这是默认的最通用形式。直接相关的 EDP 包括通过对 Delegation 的微小更改达成的 EDP。通过将方法相似性更改为相似,您得到了重定向。将对象类型相似度更改为相似度将得到 Delegated Conglomeration。仅修改对象相似性并不会给我们带来任何特别有意义的东西,因为它会导致单个对象同时施加两种不同的类型。Delegation 通常与 Retrieve 结合使用,后者提供要调用的对象。

Delegation is ubiquitous in object-oriented programming. If no other Method Invocation EDP applies, this is the most general form that will be the default. Immediately related EDPs include those reached through minor changes to Delegation. By altering the method similarity to similar, you arrive at Redirection. Changing the object type similarity to similar gives you Delegated Conglomeration. Modifying only the object similarity doesn’t give us anything particularly meaningful, because it would result in a single object having two dissimilar types imposed on it simultaneously. Delegation is often found in conjunction with Retrieve, which provides the object to be called upon.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:不同

Object Type: Dissimilar

方法:不同

Method: Dissimilar

图像

重定向

Redirection

对象行为

Object Behavioral

意图

请求另一个对象执行与手头任务密切相关的子任务,可能执行基本工作。

To request that another object perform a tightly related subtask to the task at hand, perhaps performing the basic work.

也称为

Tom Sawyer,车间领班

Tom Sawyer, Shop Foreman

赋予动机

作为对 Delegation 的一个小改进,Redirection 考虑到执行类似任务的方法通常名称相似。我们可以利用这一点来阐明此模式相对于更通用形式的意图,并展示它适用的案例。

A small refinement to Delegation, Redirection takes into consideration that methods performing similar tasks are often named similarly. We can take advantage of this to clarify the intent of this pattern over the more general form and show cases where it is applicable.

这种模式的一个例子是 Tom Sawyer,他是一位文学家,以说服其他人为他完成任务而闻名 [39]。与 DelegationExecutive 别名不同,Tom 通常寻求让其他人完成他被要求完成的任务。常见的轶事是汤姆被要求粉刷他姑姑的栅栏。他不想自己做这项工作,所以他说服了一群朋友为他做。他可以让一个朋友处理油漆桶,另一个朋友准备栅栏,等等,这样工作流程就会看起来很像高管的工作流程。相反,他决定分工的最佳方式是让每个朋友执行相同的任务:粉刷栅栏。Tom 确保每个人都有正确的工具和足够的油漆,并且任务已经完成,但他没有亲自执行任务。他要求粉刷栅栏的人每个人都做了 Tom 被要求做的相同任务——他们只是做了一小部分。

An example for this pattern is Tom Sawyer, a literary figure who was famous for convincing other people to do his tasks for him [39]. Unlike the Executive alias of Delegation, Tom generally sought to have others do exactly the task he was asked to do. The common anecdote involves Tom being asked to paint his aunt’s fence. He didn’t want to do the work himself, so he convinced a group of friends to do it for him. He could have had one friend handle paint buckets, another prep the fence, and so on, and the workflow would have looked a lot like that of an executive. Instead, he decided the best way of dividing the work was to have each friend perform the same task: paint the fence. Tom made sure that each person had the right tools and enough paint and that the task was completed, but he did not perform the task himself. Those he asked to paint the fence each did the same task Tom was asked to do—they just did smaller portions of it.

重定向也是如此。当作业可以分解为较小的子任务时,请使用 Redirection,这些子任务与主任务具有相同的基本动机和意图。请注意,不同方法的行为可能完全不同,但意图是相同的。汤姆不画画——他分发油漆和画笔——但他的目的是给栅栏画漆。同样,为他工作的人也打算粉刷栅栏——他们只关心栅栏的较小部分。清单 5.15 展示了一个 Java 示例:Tom 指示他的每个朋友在被要求时给栅栏上漆。

So it is with Redirection. Use Redirection when a job can be broken down into smaller subtasks that have the same basic motivation and intent as the main task. Note that the behavior may be radically different across the methods but that intent is the same. Tom didn’t paint—he handed out paint and brushes—but his intent was to get the fence painted. Likewise, the people working for him intended to paint the fence—they were just concerned with smaller sections of it. Listing 5.15 shows an example in Java: Tom directs each of his friends to paint the fence when he’s asked to do it.

清单 5.15.汤姆在帮助下粉刷栅栏。

Listing 5.15. Tom paints the fence with help.


   public 类朋友 {

2 public void paintTheFence(int beg, int end) {

做画

4 };

};



6 public class TomSawyer {

8 Friends are collected elsewhere

java.util.ArrayList<Friend> friends;

10

从乞求到末端

绘制栅栏 12 public void paintTheFence(int beg, int end) {

int fenceLength = end - 乞求;

14 int 子围栏 = fenceLength / friends.size();

int friendBeg = 乞求;

16 for (Friend f : friends) {

int friendEnd = friendBeg + subfence - 1;

18 f.paintTheFence(friendBeg, friendEnd);

^--- 重定向

20 friendBeg = friendEnd + 1;

}

22     };

   };

   public class Friend {

 2     public void paintTheFence(int beg, int end) {

           // Do the painting

 4     };

   };

 6

   public class TomSawyer {

 8     // Friends are collected elsewhere

       java.util.ArrayList<Friend> friends;

10

       // Paint the fence from beg to end

12     public void paintTheFence(int beg, int end) {

           int fenceLength = end - beg;

14         int subfence = fenceLength / friends.size();

           int friendBeg = beg;

16         for (Friend f : friends) {

               int friendEnd = friendBeg + subfence - 1;

18             f.paintTheFence(friendBeg, friendEnd);

               // ^--- Redirect

20             friendBeg = friendEnd + 1;

           }

22     };

   };


这种设置类似于我们在第 2 章 2.2.4 节中看到的,车间领班给汽车喷漆。请注意,在这两种情况下,调用方法实际上都不会执行所请求的工作,尽管这肯定是可能的。调用方法是纯粹充当路由器、领班还是经理,或者它是否参与实际执行任务并不重要。

This setup is similar to what we saw back in Chapter 2, Section 2.2.4, with the shop foreman painting cars. Notice that in neither case is the calling method actually doing the work requested, although it certainly is possible. Whether the calling method is acting purely as a router, a foreman, or a manager, or whether it is involved in actually performing the task is immaterial.

在其他时候,调用方对要完成的任务可能略有不同,并要求其他人完成大部分工作,然后调用方将执行准备工作或事后进行清理。如果你正在粉刷一个房间,有两种方法可以做到这一点。您可以潜入并开始用刷子绘画——并希望您不会在电灯开关、模具、插座等上涂抹太多油漆——或者您可以花时间仔细准备房间,将防水布适当地贴在踢脚板上,取下电灯开关和插座板,用胶带粘住所有成型边缘, 然后小心地更换所有东西并取下胶带和防水布。

At other times, the caller may have a slightly different idea of what the task to be completed is and ask someone else to do the bulk of the work, and the caller will then either perform prep work or do cleanup afterwards. If you’re painting a room, there are two ways you can go about doing it. You can either dive in and start painting with a brush—and hope you don’t slop too much paint on lightswitches, molding, outlets, and the like—or you can take time to carefully prepare the room by laying down tarps properly taped to the baseboards, removing the lightswitch and outlet plates, taping all the molding edges, and carefully replacing everything and removing the tape and tarps afterwards.

一个快如闪电且擅长涂漆的人不一定是那个对准备工作和清理工作细致和有条不紊的人。他们都在粉刷房间,但一个人的理念是最大限度地提高速度,而另一个人的重点是质量。在你能做到的地方同时做这两件事会很棒。在这种情况下,您可以让细心的人进行准备工作和清理,然后该人可以要求其他人进行实际的绘画以加快速度。现在,准备和清理人员可以自由地去准备另一个房间,同时第一个房间正在粉刷。这个过程可以同步建模,就像 Listing 5.16 一样。

Someone who is lightning fast and good at applying a coat of paint isn’t necessarily going to be the person who is careful and methodical about prep work and cleanup. They’re both painting the room, but one person’s concept of how to go about doing it is to maximize speed, while the other person’s focus is on quality. It would be great to do both where you can. In cases such as this, you can have the careful person perform the prep work and cleanup, and then that person can ask someone else to do the actual painting for speed. Now the prep and cleanup person is free to go prep another room while the first one is being painted. This process can be modeled synchronously as something like Listing 5.16.

清单 5.16.准备工作和清理很重要。

Listing 5.16. Prep work and cleanup are important.


 1 class SloppyFastPainter {

public:

3 void paintRoom( Room r ) {

粉刷墙壁

5 };

};



7 class CarefulPainter {

9 SloppyFastPainter sloppy;

public:

11 void paintRoom( Room r ) {

在房间

13 铺设防水布 Remove hardware from room

sloppy.paintRoom( r ); 重定向

15 更换房间

内的硬件 清理房间

17     };

   };

 1 class SloppyFastPainter {

   public:

 3     void paintRoom( Room r ) {

           // Paint the walls

 5     };

   };

 7

   class CarefulPainter {

 9     SloppyFastPainter sloppy;

   public:

11     void paintRoom( Room r ) {

           // Laydown tarps in room

13         // Remove hardware from room

           sloppy.paintRoom( r );  // Redirect

15         // Replace hardware in room

           // Clean up room

17     };

   };


不幸的是,这里存在混淆的机会。从一般意义上讲,delegate 一词的意思是将部分工作负载移交给其他人。这个最一般的含义就是 Delegation EDP 的定义。每个方法调用 EDP 都是委托的专用化,但重定向是最有可能被错误地描述为 Delegation 的。这种混淆是可以理解的,因为在软件工程中,术语 delegation 有多种用法。请记住,即使是 C# 也采用了术语 delegate 来表示最一般的情况,将其用作关键字,包装要传递的方法,同时保留我们在本文中定义的所有三个相似性轴。委派用于移交给任何其他方法,而使用 重定向 则更具体一些。

Unfortunately, there is an opportunity for confusion here. Used in the general sense, the word delegate simply means to hand off part of a workload to someone else. That most general sense is what the Delegation EDP defines. Every method-call EDP is a specialization of delegation, but Redirection is the most likely one to be mistakenly described as Delegation. This confusion is quite understandable, as the term delegation is used in a number of ways in software engineering. Just remember that even C# has adopted the term delegate for the most general case, using it as a keyword wrapping a method to be passed around while leaving open all three of the similarity axes we defined in this text. Delegation is used for handing off to any other method, while using Redirection is a bit more specific.

适用性

在以下情况下使用 Redirection

Use Redirection when:

• 另一个对象可以执行当前方法主体希望完成的某些工作。

• Another object can perform some work that your current method body wishes to have done.

• 另一个对象不需要访问当前对象中的私有数据即可完成任务。

• The other object does not need access to the private data in the current object to complete the task.

• 两个对象之间没有已知的相关类型关系。

• There is no known relevant type relationship between the two objects.

• 该目标对象具有具有类似意图的方法,通过其在 Kent Beck 的 Intent Revealing Selector 模式之后的签名名称表示。

• That target object has a method that has a similar intent, expressed through its signature name after Kent Beck’s Intention Revealing Selector pattern.

结构
图像
参与者

重定向器

Redirector

方法调用的原始站点包含一个名为 operation 的方法,该方法具有要分包到另一个对象的 subtask。此对象 , 是 类型的 field 元素。redirectTargetRedirectee

The originating site of the method call contains a method named operation, which has a subtask to be parceled out to another object. This object, redirectTarget, is a field element of type Redirectee.

重定向对象

Redirectee

消息接收方的类型,该接收者执行请求的 subtask。

The type of the receiver of the message, which performs the subtask asked of it.

操作

operation

重定向器的操作调用 operation 来执行其部分工作,从而调用 的 操作版本。redirectTarget.Redirectee

Redirector’s operation calls redirectTarget. operation to perform a portion of its work, and thereby invokes Redirectee’s version of operation.

合作

委托(实际上是所有方法调用 EDP)一样,Retrieve 描述了两个对象及其封闭方法之间的二进制关系。在这种情况下,它使用相似方法之间的调用来定义两个不同类型的不同对象之间的关系。命名的相似性,以及因此假定的意图,为他们关系的性质提供了线索。

As with Delegation (and indeed all the Method Invocation EDPs), Retrieve describes a binary relationship between two objects and their enclosed methods. In this case, it defines a relationship between two dissimilar objects of dissimilar types, using a call between similar methods. The similarity of the naming, and therefore assumed intent, provides a clue to the nature of their relationship.

后果

尽管 RetrieveDelegation 模式几乎相同,但方法相似的看似很小的额外要求具有一些深远的影响。我们将在后面的 Pattern 中看到,当此 Pattern 与其他 EDP 和类型信息相结合时,可以快速形成并简单地描述复杂的交互。

Even though Retrieve is almost identical to the Delegation pattern, the seemingly small additional requirement that the methods be similar has some far-reaching effects. We will see in later patterns that when this pattern is combined with other EDPs and typing information, complex interactions can quickly be formed and described simply.

通过利用两个方法具有相同的名称以及这是声明方法意图的常用方式这一事实,我们可以推断出这两个方法具有共同的功能意图。此外,很明显,这是一种适当的方式,可以指示我们的原始调用点方法正在请求被调用的方法来执行某些部分工作,并且该工作与原始方法的核心功能密切相关。

By leveraging the facts that both methods have the same name and that this is a common way of declaring the intent of a method, we can deduce that the two methods have a common functional intent. Furthermore, it becomes obvious that this is an appropriate way to indicate that our originating call-site method is requesting the invoked method to do some portion of work, and that work is tightly related to the core functionality of the original method.

被调用的对象可能驻留在调用方法中或由调用方法拥有,该对象包含调用方法,或者它可能通过使用 Retrieve 来自其他位置。的 Implementation 部分显示多个表单。

The object being called upon may reside in or be owned by the calling method, the object enclosing the calling method, or it may come from elsewhere via a use of Retrieve. The Implementation section shows multiple forms.

如果小心地完成,此行为可用于形成 Inheritance 的临时变体,方法是使用具有单个目标对象的多个 Redirection 实例将一个对象拼接到具有相同(或非常相似)接口的另一个对象。调用方法可能只是传递给 ,模拟继承的实现,或者它们可能提供自己的方法体,模拟继承实现的覆盖,但破坏 Redirection。如果它们都提供自己的方法体,并在该体中调用相应的目标方法,那么这不仅是重定向,而且还是相关 Extend Method 的特席形式。RedirecteeRedirectee

If done carefully, this behavior can be used to form an ad hoc variant of Inheritance by stitching one object to another with the same (or very similar) interface using multiple Redirection instances with a single target object. The calling methods may just pass through to the Redirectee, mimicking an inherited implementation, or they may provide their own method body, mimicking the overriding of an inherited implementation, but breaking the Redirection. If they both provide their own method body, and in that body call the appropriate target method in the Redirectee, then not only is this a Redirection, but it is also an ad hoc form of the related Extend Method.

实现

在 Java 中:

In Java:


   public 类Foo {

2 public void operation();

};



4 公共类 Bar {

6 Foo f;

public void operation() {

8 Foo f2;

f.操作 (); 重定向

10 f2.operation(); 重定向

};

12 };

   public class Foo {

 2     public void operation();

   };

 4

   public class Bar {

 6     Foo f;

       public void operation() {

 8         Foo f2;

           f.operation();  // Redirect

10         f2.operation(); // Redirect

       };

12 };


在 Objective-C 中,说明了如何使用 Retrieve 来获取对 Redirectee 的引用:

In Objective-C, illustrating the use of Retrieve to get the reference to the Redirectee:


   @interfaceFoo

2 {

}

4 -(void) 操作;



@end 6

@implementation Foo

8 -(void) operation {

Do work

10 };



@end 12

@interface Goo

14 {

Foo* f;

16 }

-(Foo*) getFoo;

18 @end



20 @implementation Goo

-(Foo*) getFoo {

22 return f;

};

24 @end



26 @interface bar

{

28 粘液* g;

}

30 -(void) 操作;



@end 32

@implementation

34 -(void) 操作 {

[[g getFoo] 操作]; 重定向于 Retrieve

36 }

@end

   @interface Foo

 2 {

   }

 4 -(void) operation;

   @end

 6

   @implementation Foo

 8 -(void) operation {

       // Do work

10 };

   @end

12

   @interface Goo

14 {

       Foo* f;

16 }

   -(Foo*) getFoo;

18 @end



20 @implementation Goo

   -(Foo*) getFoo {

22     return f;

   };

24 @end



26 @interface Bar

   {

28     Goo* g;

   }

30 -(void) operation;

   @end

32

   @implementation Bar

34 -(void) operation {

       [[g getFoo] operation];  // Redirect on Retrieve

36 }

   @end


相关模式

重定向是另一种非常通用的 EDP。它与 Delegation 的唯一区别在于,这些方法现在在意图和命名方面具有相似性。改变这种相似性,你就回到了 Delegation。另请参阅 检索 有关如何通过收紧对象类型相似性来让目标对象在运行时发生变化的建议。与 Delegation 一样,仅将对象相似性更改为 similaring,而将对象类型相似性保留为 dissimilar 会导致未知状态。

Redirection is another very general EDP. It differs from Delegation only in that the methods now have a similarity of intent and naming. Alter that similarity, and you’re back to Delegation. Also see Retrieve for advice on how to let the target object vary at runtime by tightening the object type similarity. As with Delegation, changing just the object similarity to similar while leaving the object type similarity as dissimilar results in an unknown state.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:不同

Object Type: Dissimilar

方法:类似

Method: Similar

图像

聚集

Conglomeration

对象行为

Object Behavioral

意图

将不同的操作和行为汇集在一起或组合在一起,以便在单个对象中完成更复杂的任务。

To bring together, or conglomerate, diverse operations and behaviors in order to complete a more complex task within a single object.

也称为

分解消息、帮助程序方法

Decomposing Message, Helper Methods

赋予动机

通常要求对象执行一个太大或太笨重的任务,而该任务无法在单个方法中执行。通常,将任务分解为较小的部分,作为离散方法单独处理,然后由负责较大任务的方法将它们构建回整个结果,这在概念上是有意义的。Kent Beck 将此称为 Decomposing Message 模式 [5]。也可能发生相关 subtask 可以统一为单个方法的情况,从而导致在单个对象中重用代码。

An object is often asked to perform a task that is too large or unwieldy to be performed within a single method. Usually, it makes conceptual sense to break the task into smaller parts to be handled individually as discrete methods, and then build them back into a whole result by the method responsible for the larger task. Kent Beck refers to this as the Decomposing Message pattern [5]. It may also happen that related subtasks can be unified into single methods, resulting in the reuse of code inside a single object.

Conglomeration 对较小操作的重组有几个好处。它优化了通过让每个方法执行较少的工作来触发的操作的粒度。这允许在多个位置重用这些方法,而无需在多个位置复制和粘贴代码,从而提高可维护性。此外,如果这些细粒度方法公开可见,则其他对象可能能够以新的方式使用行为的某些部分。

The recomposition of smaller actions by Conglomeration has several benefits. It refines the granularity of actions that can be triggered by having each method perform less work. This can improve maintainability by allowing these methods to be reused in multiple places without copying and pasting the code in several places. Additionally, other objects may be able to use portions of the behavior in new ways if these fine-grained methods are made publicly visible.

考虑 Listing 5.17 中的示例,它建立在 Redirection 中的一个代码示例之上。我们认识到粉刷房间的方法太长了;要执行的步骤太多,如果代码被分解成更小的任务,则代码更容易阅读。我们公开 subtask 方法,因为很明显,设置和清理房间的操作很有价值,并且可以独立于粉刷房间的操作来请求。现在,他们可以在天花板修补机或石膏板砂光机进来之前进行准备,也可以在之后进行清理。曾经是行为中不可分割的一部分,现在被分解为有用的小动作。CarefulPainterpaintRoom

Consider the example in Listing 5.17, which builds on one of the code examples from Redirection. We recognized that the method for painting a room is simply too long; there are too many steps to be taken, and the code is easier to read if it is broken up into smaller tasks. We make the subtask methods publicly available because it becomes obvious that the actions of setting up and cleaning up a room are valuable and can be requested independently of the act of painting a room. Now, the CarefulPainter can do the preparation before a ceiling refinisher or a drywall sander comes in and can also perform the cleanup afterwards. What was once inextricably part of the paintRoom behavior is now broken out into useful smaller actions.

清单 5.17.准备工作和清理是可分解的。

Listing 5.17. Prep work and cleanup are decomposable.


 1 class SloppyFastPainter {

public:

3 void paintRoom( Room r ) {

粉刷墙壁

5 };

};



7 class CarefulPainter {

9 SloppyFastPainter sloppy;

房间当前TarpRoom;

11 房间 currentHardwareRoom;

public:

13 void paintRoom( 房间 r ) {

this->laydownTarps( r );

集合 15 this->removeHardware( r ); 砾岩

sloppy.paintRoom( r );

17 this->replaceHardware();

集合 this->cleanUp(); 砾岩

19 };

void laydownTarps( 房间 r ) {

21 this->currentTarpRoom = r;

躺下篷布

23 };

void cleanUp() {

25 Clean currentTarpRoom

释放 currentTarpRoom

27 };

void removeHardware( 房间 r ) {

29 this->currentHardwareRoom = r;

删除硬件

31 };

void replaceHardware() {

33 替换 currentHardwareRoom

中的硬件 释放 currentHardwareRoom

35     };

   };

 1 class SloppyFastPainter {

   public:

 3     void paintRoom( Room r ) {

           // Paint the walls

 5     };

   };

 7

   class CarefulPainter {

 9     SloppyFastPainter sloppy;

       Room currentTarpRoom;

11     Room currentHardwareRoom;

   public:

13     void paintRoom( Room r ) {

          this->laydownTarps( r );   // Conglomeration

15        this->removeHardware( r );  // Conglomeration

          sloppy.paintRoom( r );

17        this->replaceHardware();// Conglomeration

          this->cleanUp();         // Conglomeration

19     };

       void laydownTarps( Room r ) {

21         this->currentTarpRoom = r;

           // Laydown tarps

23     };

       void cleanUp() {

25         // Clean currentTarpRoom

           // Relinquish currentTarpRoom

27     };

       void removeHardware( Room r ) {

29         this->currentHardwareRoom = r;

           // Remove hardware

31     };

       void replaceHardware() {

33         // Replace hardware in currentHardwareRoom

           // Relinquish currentHardwareRoom

35     };

   };


在此过程中,我们必须做出决定。我们可以允许将 room 作为参数发送到每个子任务。这允许最大的灵活性,但让我们暂时假设这里有雇主和工会的规定,其中一个是,出于问责制的原因,同一个人必须执行准备和最终确定任务。这样可以防止在移除过程中丢失的硬件被归咎于更换它的人,等等。每个人都完成他或她开始的工作。但是,如果不能正确跟踪这些数据,可能会导致混淆。

In doing so, we had a decision to make. We could have allowed the room to be sent along as a parameter to each and every of the subtasks. This allows for the greatest flexibility, but let’s assume for a moment that there are employer and union regulations at work here, and one is that, for accountability reasons, the same person must perform the preparation and finalization tasks. This prevents, say, hardware lost during removal from being blamed on the person replacing it, and so on. Each person finishes the job he or she initiated. Not properly tracking this data can, however, lead to confusion.

如果一个无组织的现场经理有几个人在跑来跑去,并负责告诉他们每个步骤要去哪个房间,他们可能会不小心派一个人来清理另一个人准备好的房间。相反,我们选择将会议室信息设为每个 .可以想象,工地经理告诉员工去,然后递给他或她一张带有房间号的纸条。支票放在工人的口袋里,当被告知时,他或她只需参考支票即可确保清理正确的房间。完成后,工人放弃 chit 并准备进入另一个房间。chit 表示的数据是 worker 专用的,并将可以要求 worker 完成的单独任务联系在一起。CarefulPainterCarefulPainterlaydownTarpscleanUp

If an unorganized site manager had several CarefulPainters running around and was responsible for telling them which room to go do each step in, they could accidentally send one to clean up a room that another had prepped. Instead, we choose to make the room information private to each CarefulPainter. Think of this as if the site manager tells a worker to go laydownTarps and hands him or her a chit with the room number. The chit is kept in the worker’s pocket, and when told to cleanUp, he or she simply refers to the chit to make sure the right room gets cleaned up. Once completed, the worker relinquishes the chit and is ready for another room. The data the chit represents is private to the worker and ties together separate tasks that worker can be asked to do.

企业集团功能强大,但如果走得太远,可能会导致问题。不合逻辑的结论是将每个语句都放在自己的方法中,但为什么这是一个坏主意应该很明显。无论何时应用 Conglomer,都必须在细粒度的必要性与直接表达代码的简化和效率之间取得平衡。找到正确的 “原子行为” 需要知道你的原子必须有多大。

Conglomeration is powerful, but it can cause issues if taken too far. The illogical conclusion would be to place every statement within its own method, but it should be obvious why that’s a bad idea. Any time Conglomeration is applied, a balance must be struck between the necessity for fine granularity and the simplification and efficiency of directly expressed code. Finding the proper “atomic behavior” requires knowing how large your atoms must be.

适用性

在以下情况下使用 Conglomeration

Use Conglomeration when:

• 可以将大型任务分解为较小的子任务。

• A large task can be broken into smaller subtasks.

• 子任务必须在单个对象实例上执行,通常是由于共享的私有数据或状态。

• The subtasks must be performed on a single object instance, usually due to shared private data or state.

• 多个子任务可以统一到一个方法主体中。

• Several subtasks may be unified into a single method body.

结构
图像
参与者

集聚体

Conglomerator

封闭对象类型。

Enclosing object type.

操作

operation

掌握分配子任务的控制方法。

Master controlling method that parcels out subtasks.

操作 2

operation2

执行特定 subtask 的 Subservient 方法。

Subservient method performing a particular subtask.

合作

Delegation 的专用化中,对象调用自身的方法。调用站点是 operation,被调用的方法是 operation2

In a specialization of Delegation, the object calls a method of itself. The calling site is operation, and the method being called is operation2.

后果

Delegation 一样,此模式将两种方法捆绑成一种依赖关系,其中 operation 依赖于 operation2 的行为和实现。在这种情况下,与 Delegation 不同,它们共享的对象范围内的共享数据可能会立即产生副作用。

As with Delegation, this pattern ties two methods into a reliance relationship in which operation relies on the behavior and implementation of operation2. In this case, unlike Delegation, there may be immediate side effects on shared data within the confines of the object that they share.

实现

在 Java 中:

In Java:


  public 类企业集团 {

2 public void operation() {

可选准备工作

4 operation2();

可选 finish work

6 };

公共 void operation2() {};

8 }

  public class Conglomerate {

2     public void operation() {

          // Optional prep work

4         operation2();

          // Optional finish work

6     };

      public void operation2() {};

8 }


在 Python 中:

In Python:


  Conglomerate:

2 def operation(self):

# 可选准备工作

4 self.operation2();

# 可选 finish work

6 def operation2(self):

# 请求行为

8 pass

  class Conglomerate:

2     def operation(self):

          # Optional prep work

4         self.operation2();

          # Optional finish work

6     def operation2(self):

          # Requested behavior

8         pass


相关模式

集合将单个对象中的方法捆绑在一起,因此是我们见过的第一个没有依赖于 Retrieve 的表单的 EDP。修改对象相似性,以便调用另一个对象,同时保持相同的对象类型相似性,从而导致使用 Delegated Conglomeration。反转此属性以保留对象相似性,并将对象类型相似性更改为子类型关系会导致 Revert 方法。这听起来可能有点奇怪 — 具有相同的对象但不是相同的类型 — 但请阅读 Revert Method 规范,了解为什么这不仅可能而且非常有用。另一方面,在这个时候,保持对象相似性但将对象类型相似性更改为完全不同是错误的。最后,更改方法相似性,以便调用类似的方法会导致递

Conglomeration ties methods within a single object together and therefore is the first EDP we’ve seen that doesn’t have a form that relies on Retrieve. Modifying the object similarity so that the call is to another object, while retaining the same object type similarity, results in a use of Delegated Conglomeration. Reversing this to retain the object similarity, and changing the object type similarity to a subtyping relationship leads to Revert Method. This may sound a bit bizarre—having the same object but not the same type—but read the Revert Method specification for why this is not only possible but also highly useful. On the other hand, keeping the object similarity but changing the object type similarity to completely dissimilar is, at this juncture, ill formed. Finally, changing the method similarity such that the call is to a similar method results in Recursion.

集合将出现在对象在内部分解任务的任何位置,但有关此 EDP 的相当独特的用法,请参阅第 7 章中的模板方法

Conglomeration will appear anywhere that an object is breaking down a task internally, but for a rather unique use of this EDP, see Template Method in Chapter 7.

方法调用分类

对象:类似

Object: Similar

对象类型:类似

Object Type: Similar

方法:不同

Method: Dissimilar

图像

递归

Recursion

对象行为

Object Behavioral

意图

通过执行许多较小的类似任务,同时使用相同的对象状态来完成较大的任务。

To accomplish a larger task by performing many smaller and similar tasks while using the same object state.

赋予动机

有时,经过考虑,我们发现一个问题可以分解为与原始任务相同的子任务,只是规模较小。使用合并排序算法对项目数组进行排序就是这样一个示例。合并排序采用一个数组并将其分成两半,分别对每个数组进行排序,然后将两个排序的数组合并为一个统一的整体。两个 half 问题也使用合并排序进行排序,因此它们受到相同的问题减半,依此类推。最终,到达单个项目的数组,此时合并开始。

Sometimes, after consideration, we find that a problem can be broken down into subtasks that are identical to the original task, except on a smaller scale. Sorting an array of items using the merge sort algorithm is one such example. Merge sort takes an array and divides it into halves, sorting each individually, and then merges the two sorted arrays into a unified whole. The two half-problems are also sorted using merge sort, so they are subject to the same halving of the problem, and so on. Eventually, arrays of a single item are reached, at which point the merging begins.

方法调用自身的过程称为递归,它在一般编程中无处不在。同样的原则也适用于面向对象编程,但我们有一个额外的要求,即对象必须通过隐式或显式地使用 self 来调用自身。您几乎可以肯定对这个概念很熟悉,但它在 EDP 目录中具有特定的位置和上下文。

The process by which a method calls itself is known as recursion, and it is ubiquitous in general programming. The same principle applies in object-oriented programming, but we have the additional requirement that the object must be calling on itself through implicit or explicit use of self. You’re almost certainly familiar with this concept, but it has a specific placement and context within the EDP catalog.

一般来说,递归是一种将大量计算折叠到一个小的概念空间中的方法。假设我们想要对一个数组进行排序,并且我们决定对一个大型数组进行排序的一种简单方法是将其拆分为两个大小相似的数组,然后在单独排序后合并两个子数组。合并将很容易:如果数组 A 的头部小于数组 B 的头部,则 A 的头部被复制到新的、更大的数组中;否则,复制 B 的 head。将复制的项从相应的数组中删除,并重复该过程,直到两个数组合并。

Generally speaking, recursion is a way of folding a large amount of computation into a small conceptual space. Assume we want to sort an array, and we decide that a simple way to sort a large array is to split it into two arrays of similar size and then merge the two subarrays after they are individually sorted. The merging will be easy: if the head of array A is less than the head of array B, then the head of A is copied to the new, larger array; otherwise, the head of B is copied. The copied item is removed from the appropriate array, and the process is repeated until both arrays are merged.

因为合并排序方案依赖于子数组的正确排序,所以我们正确地推测,我们可以通过依次拆分、排序和合并每个子数组来对两个子数组执行排序算法。我们再次面临相同的排序问题,因此我们以相同的方式继续,直到我们到达最小的不可分割数组:单个项目。此时,可以开始在每个步骤中合并已排序的子数组的过程。

Because the merge sort scheme relies on the proper sorting of the sub-arrays, we correctly surmise that we can perform the sort algorithm on the two subarrays by splitting, sorting, and then merging each in turn. We are again faced with the same sorting problem, so we continue in the same manner until we reach the smallest indivisible array: a single item. At that point, the process of merging the sorted subarrays at each step can begin.

假设我们有一个类,其中包含 和 的常用方法。如果起始数组的长度为 4 项,那么我们可以像下面的伪代码一样对整个排序进行硬编码,请记住,在面向对象的系统中,默认情况下我们应该有一个封闭的类或对象。Arrayaddremove

Assume we have an Array class that contains the usual methods of add and remove. If the length of the beginning array was 4 items, then we could hardcode the entire sorting as in the following pseudocode, remembering that in an object-oriented system we should have an enclosing class or object by default.


   ArrayOfLength4Library {

2 数组 sort_merge(数组 a) {

元素 a11 = a[0];

4 元素 a12 = a[1];

元素 a21 = a[2];

6 元素 a22 = a[3];

数组 a1, a2, res;

8 对上半部分

进行排序 if (a11 < a12) {

10 a1.add(a11);

a1.add(a12);

12 } else {

a1.add(a12);

14 a1.add(a11);

}

16 排序后半部分

if (a21 < a22) {

18 a2.add(a21);

a2.add(a22);

20 } else {

a2.add(a22);

22 a2.add(a21);

}

24 合并

if (a1[0] < a2[0]) {

26 res.add(a1[0]);

a1.remove(0);

28 } else {

res.add(a2[0]);

30 a2.remove(0);

}

32 if (a1[0] < a2[0]) {

res.add(a1[0]);

34 a1.remove(0);

} else {

36 res.add(a2[0]);

a2.remove(0);

38 }

if (a1.length == 0) {

40 res.add(a2[0]);

res.add(a2[1]);

42 }

if (a2.length == 0) {

44 res.add(a1[0]);

res.add(a1[1]);

46 }

if (a1.length == 1 && a2.length == 1) {

48 res.add(a1[0]);

res.add(a2[0]);

50 }

返回 a;

52     };

   };

   class ArrayOfLength4Library {

 2     Array sort_merge(Array a) {

           Element a11 = a[0];

 4         Element a12 = a[1];

           Element a21 = a[2];

 6         Element a22 = a[3];

           Array a1, a2, res;

 8         // Sort first half

           if (a11 < a12) {

10             a1.add(a11);

               a1.add(a12);

12         } else {

               a1.add(a12);

14             a1.add(a11);

           }

16         // Sort second half

           if (a21 < a22) {

18             a2.add(a21);

               a2.add(a22);

20         } else {

               a2.add(a22);

22             a2.add(a21);

           }

24         // Merge

           if (a1[0] < a2[0]) {

26             res.add(a1[0]);

               a1.remove(0);

28         } else {

               res.add(a2[0]);

30             a2.remove(0);

           }

32         if (a1[0] < a2[0]) {

               res.add(a1[0]);

34             a1.remove(0);

           } else {

36             res.add(a2[0]);

               a2.remove(0);

38         }

           if (a1.length == 0) {

40             res.add(a2[0]);

               res.add(a2[1]);

42         }

           if (a2.length == 0) {

44             res.add(a1[0]);

               res.add(a1[1]);

46         }

           if (a1.length == 1 && a2.length == 1) {

48             res.add(a1[0]);

               res.add(a2[0]);

50         }

           return a;

52     };

   };


虽然效率很高,但这种解决方案有一个明显的缺点:它仅限于适用于长度为 4 的数组。一个更灵活的版本是使用循环的版本。这里我们说明了一个循环实现,为简单起见,假设数组的长度是 2 的幂:for

While highly efficient, this solution has an obvious drawback: it is limited to only working for arrays of length 4. A much more flexible version is one using looping. Here we illustrate a for loop implementation, assuming for simplicity that the length of the array is a power of 2:


 1 class ArrayOfPower2LengthLibrary {

数组 sort_array(Array a) {

3 将 a 切成子数组

subarray[0][0] = a;

5 for (i = 1 to log_2(a.length())) {

for (j = 0 to 2^i) {

7 prevarray = subarray[i-1][floor(j/2)]

subarray[i][j] = prevarray.slice(

9 ((j mod 2) *

(prevarray.length() / 2)),

11 ((j mod 2) + 1) *

(prevarray.length() / 2))

13 }

}

15 对子数组进行排序并合并

for (i = log_2(a.length()) to 1) {

17 for (j = 2^i to 0) {

subarray[i-1][j] =

19 max(subarray[i][j],

subarray[j][i]);

21 }

}

23 return subarray[0][0];

       };

 1 class ArrayOfPower2LengthLibrary {

        Array sort_array(Array a) {

 3           // Slice a into subarrays

             subarray[0][0] = a;

 5           for (i = 1 to log_2(a.length())) {

                  for (j = 0 to 2^i) {

 7                     prevarray = subarray[i-1][floor(j/2)]

                       subarray[i][j] = prevarray.slice(

 9                          ((j mod 2) *

                                (prevarray.length() / 2)),

11                          ((j mod 2) + 1) *

                                (prevarray.length() / 2))

13                }

           }

15         // Sort subarrays and merge

           for (i = log_2(a.length()) to 1) {

17              for (j = 2^i to 0) {

                     subarray[i-1][j] =

19                        max(subarray[i][j],

                               subarray[j][i]);

21             }

           }

23         return subarray[0][0];

       };


这个解决方案成功地使我们的算法更加灵活,但由于开销机制,代价是它几乎不可读。请注意,我们向内循环到基本情况(数组的长度是一个单位),在每一步存储 state ,然后使用之前存储的 state 向外循环。这个 enter、store、use store 和 unroll 的过程正是函数调用所完成的。我们可以利用这一事实来大大简化我们使用递归的实现:

This solution succeeds in making our algorithm much more flexible, but at a cost of making it almost unreadable because of the overhead mechanisms. Note that we are looping inward to the base case (length of array is a single unit), storing state at every step, then looping outward using the state previously stored. This process of enter, store, use store, and unroll is precisely what a function call accomplishes. We can use this fact to vastly simplify our implementation using Recursion:


   ArrayLibrary {

2 数组排序 (数组 a) {

return sort_array(a,

4 a.begIndex(), a.endIndex());

}

6 数组 sort_merge(数组 a, int beg, int end) {

if (a.length() > 1) {

8 数组 firstHalf =

sort_array(a, beg, end-beg/2);

10 ^--- 递归(隐式自我)

数组 secondHalf =

12 this->sort_array(a,

(end-beg/2) + 1, end);

14 ^--- 递归(显式 self)

return merge(firstHalf, secondHalf);

16 } else {

return a;

18 }

};

20

数组 merge(数组 a, 数组 b) {

22 数组 res;

while (b.length() > 1 || a.length() > 1) {

24 如果 a 或 b 为空,则 a[0] 或 b[0]

返回最小值

26 if (a[0] < b[0]) {

res.add(a[0]);

28 a.delete(0);

} else {

30 res.add(b[0]);

b.删除 (0);

32 }

}

34 返回 res;

};

36 元 };

   class ArrayLibrary {

 2     Array sort (Array a) {

           return sort_array(a,

 4                     a.begIndex(), a.endIndex());

       }

 6    Array sort_merge(Array a, int beg, int end) {

          if (a.length() > 1) {

 8           Array firstHalf =

                 sort_array(a, beg, end-beg/2);

10               // ^--- Recursion (implicit self)

             Array secondHalf =

12               this->sort_array(a,

                                   (end-beg/2) + 1, end);

14               // ^--- Recursion (explicit self)

             return merge(firstHalf, secondHalf);

16        } else {

             return a;

18        }

      };

20

      Array merge(Array a, Array b) {

22        Array res;

          while (b.length() > 1 || a.length() > 1) {

24            // If a or b is empty, then a[0] or b[0]

              // returns a min value

26            if (a[0] < b[0]) {

                  res.add(a[0]);

28                a.delete(0);

              } else {

30                res.add(b[0]);

                  b.delete(0);

32            }

          }

34        return res;

      };

36 };


这个版本在概念上更简洁,并且执行与我们的循环变体相同的任务。我们允许语言的运行时为我们处理开销。作为奖励,我们不再局限于某些长度。代码不仅更简洁,而且具有高度的通用性。

This version is conceptually cleaner and performs the same task as our looping variation. We allow the runtime of the language to handle the overhead for us. As a bonus, we’re no longer limited to certain lengths. Not only is the code cleaner, it is highly generalized.

有时,例如在高性能嵌入式系统中,这种开销处理方式可能超出了我们的需求。在这种情况下,可以将递归优化为循环,或进一步优化为线性代码。然而,这些实例是极端的,并且往往是高度专业化的。在大多数情况下,递归的好处 — 概念上的整洁和简单的代码 — 大大超过了速度的微小损失。

There are times, such as in a high-performance embedded system, when that manner of overhead handling may be excessive for our needs. In such cases, optimizing away the recursion into loops, or further into linear code, is a possibility. These instances are extreme, however, and tend to be highly specialized. In most cases, the benefits of Recursion—conceptual cleanliness and simple code—greatly outweigh the small loss of speed.

适用性

在以下情况下使用递归

Use Recursion when:

• 一个任务可以划分为高度相似的子任务。

• A task can be divided into highly similar subtasks.

• 子任务必须由同一对象执行或首选由同一对象执行,通常是因为需要通过调用方法和被调用方法访问公共存储状态。

• The subtasks must be, or are preferred to be, performed by the same object, usually because of a need for access to common stored state by both the calling and called methods.

• 由此产生的简单性增益掩盖了效率的微小损失。

• A small loss of efficiency is overshadowed by the resulting gain in simplicity.

参与者

Recursor (解析器)

Recursor

Recursor有一个方法,该方法在同一实例化中回调自身。

Recursor has a method that calls back on itself within the same instantiation.

操作

operation

operation 是通过 self.operation() 调用自身的方法,无论是隐式的还是显式的。

operation is the method that calls itself, through self.operation(), whether implicitly or explicitly.

结构
图像
合作

Recursor的方法操作与自身协作,请求在每个步骤中执行越来越小的任务,直到达到基本情况,此时结果将收集到最终结果中。

Recursor’s method operation collaborates with itself, requesting that smaller and smaller tasks be performed at each step, until a base case is reached, at which point results are gathered into a final result.

后果

Recursion 依赖于正确形成的基本情况来终止递归堆栈是这种模式的最弱点。在大多数现代系统中,肆意消耗所有可用资源几乎是不可能的,但是递归可以通过拥有一个永远不满足的格式错误的基本情况来轻松做到这一点。

The reliance of Recursion on a properly formed base case for termination of the recursion stack is the weakest point of this pattern. It is nearly impossible in most modern systems to wantonly consume all available resources, but recursion can do it easily by having a malformed base case that is never satisfied.

实现

在 Java 中:

In Java:


1 public class Recursor {

public void operation() {

3 可选的先前工作

self.operation();

5 可选的完成工作

};

7 }

1 public class Recursor {

      public void operation() {

3         // Optional prior work

          self.operation();

5         // Optional finish work

      };

7 }


相关模式

递归可能是最不通用的方法调用 EDP,因为它指定对象、对象类型和方法相似性都设置为 similar。每个都可以放宽以覆盖新的 EDP。聚合是通过在同一对象和对象类型中使用其他方法,同时使用与调用方不同的方法来实现的。(前面的最后一个示例包含 Conglomeration 的实例,包括调用 from to 和调用 from to 。保留方法相似性但允许对同一对象类型的其他实例进行调用会导致重定向递归,这是对象链中常见的概念。也许 Recursion 最有趣的邻居 EDP 是 Extend Method,当方法和对象相似性被保留,但对象类型相似性被设置为子类型关系时,会遇到这种情况。sort_mergesortsort_mergesort_mergemerge

Recursion is perhaps the least general method-call EDP, in that it specifies that object, object type, and method similarity are all set to similar. Each can be relaxed to reach new EDPs. Conglomeration is achieved by using other methods within the same object and object type while using methods that are dissimilar to the caller. (The final sort_merge example from earlier contains instances of Conglomeration, including the call from sort to sort_merge and the call from sort_merge to merge.) Retaining the method similarity but allowing the call to occur to other instances of the same object type results in a Redirected Recursion, a concept commonly seen in chains of objects. Perhaps the most interesting neighbor EDP of Recursion is Extend Method, which is encountered when the method and object similarity are preserved but the object type similarity is set to a subtyping relationship.

最后,委托 (Delegation) 有一个专门的用途:当两个对象通过两种不同的方法相互调用以共同完成一项任务时,但使用单独的私有数据。对象 A 的方法调用对象 B 上的 method,然后依次调用对象 A 上的 method。这称为 互递归,因为每个方法都间接调用自身。虽然不是递的真实示例,但它在概念上足够相似,值得一提。f()g()g()f()

Finally, there is a specialized use of Delegation: when two objects mutually call each other via two different methods to work through a task together, but with separated private data. Object A’s method f() calls method g() on object B, and in turn g() calls method f() on object A. This is known as mutual recursion because each method calls itself indirectly. While not a true example of Recursion, it is conceptually similar enough to warrant mentioning.

方法调用分类

对象:类似

Object: Similar

对象类型:类似

Object Type: Similar

方法:类似

Method: Similar

图像

还原方法

Revert Method

对象行为

Object Behavioral

意图

绕过当前类的方法实现,改用超类的实现,恢复到不太专业化的方法主体。

Bypass the current class’s implementation of a method, and use the superclass’s implementation instead, reverting to less specialized method body.

赋予动机

多态性有时会对我们不利。在 Inheritance 的模式规范中,您了解了在重用实现期间减少维护问题是该模式的驱动因素。应用于方法的基本实现的修复会自动应用于子类,但允许子类覆盖现有方法或通过 Extend Method 添加新功能。但是,我们并不总是希望修复程序自动传播,并且我们并不总是调用类似的方法。有时我们需要使用继承树上层的方法的先前实现,而不是类定义的本地实现。

Polymorphism sometimes works against us. In the pattern specification for Inheritance, you learned how reducing maintenance issues during reuse of an implementation is a driving factor for that pattern. Fixes applied to the base implementation for a method are auto applied to subclasses, but subclasses are allowed either to override existing methods or add new functionality through Extend Method. We don’t always want fixes to be propagated automatically, however, and we’re not always calling a similar method. Sometimes we need to use prior implementations of a method from higher up our inheritance tree instead of the one local to our class definition.

一个这样的实例是当提供多个版本的类以便在系统内同时使用时。假设有一个用于 Internet 数据传输协议的类库。基础库作为 1.0 提供。使用 1.1 库,对底层协议进行了更改,使用 1.1 协议的应用程序在检测到连接另一端的应用程序仅启用了 1.0 时,必须能够回退到 1.0 协议。

One such instance is when providing multiple versions of classes for simultaneous use within a system. Imagine a library of classes for an Internet data transfer protocol. A base library is shipped as 1.0. With the 1.1 library, changes to the underlying protocol are made, and an application using the 1.1 protocol must be able to fall back to the 1.0 protocol when it detects that the application at the other end of the connection is only 1.0 enabled.

一种方法是直接使用多态性并定义一个抽象提供协议方法的基类,如图 5.1 所示,并在协议检测时创建适当的类项。不幸的是,这种方法没有提供很多动态灵活性,并且不允许协议动态适应不断变化的需求。

One approach would be to use polymorphism directly and define a base class that abstractly provides the protocol’s methods, as in Figure 5.1, and create the proper class item on protocol detection. Unfortunately, this approach does not offer a lot of dynamic flexibility and would not allow a protocol to adapt on the fly to changing demands.

图像

图 5.1.多态方法

Figure 5.1. Polymorphic approach

尽管它使使用旧协议与 Client 端建立连接变得简单,但它使得在出现错误时正常降级连接的问题更加严重。假设您正在传输视频流,如果网络条件良好且延迟较低,则可以为数据引入一种新的处理器密集型压缩算法。但是,如果延迟较高,则另一端解压缩所需的 CPU 时间将导致跳过。如果您注意到网络拥塞,请下拉到较旧的压缩系统。

Although it makes setting up a connection with client using the older protocol simple, it makes graceful degradation of the connection in case of an error more problematic. Let’s say you’re transmitting a video stream, and if network conditions are good and latency is low, then a new processor-intensive compression algorithm for the data can be brought to bear. If latency is high, however, then the CPU time needed for decompression at the other end would cause skipping. If you notice that the network is congested, you drop down to the older compression system.

您可以实例化每种协议类型的对象,并根据需要交换到早期版本,但可能会出现协议状态问题,并且很麻烦。从一个协议处理程序类转换为另一个协议处理程序类需要实例化新类的新对象,然后每次需要可能的协议转换时复制任何私有数据。希望这些数据可以只保存在类中,但即使这样,也必须打破私有数据的严格封装才能完成复制。此外,这将 class 和 class 之间的协议知识分开,如 Listing 5.18 所示。前一个类实现该实际行为,但后者必须知道每个行为何时合适。ProtocolBaseProtocolController

You could instantiate objects of each protocol type and swap to earlier versions as needed, but protocol state issues might arise and be troublesome. Converting from one protocol handler class to another would require instantiating a new object of the new class and then copying over any private data each and every time a possible protocol shift was needed. Hopefully this data can be held exclusively in the ProtocolBase class, but even then there has to be a breaking of strict encapsulation of the private data to accomplish the copy. Further, this splits the protocol knowledge between the Protocol classes and the Controller class, as shown in Listing 5.18. The former classes implement that actual behavior, but the latter has to know when each behavior is appropriate.

清单 5.18.在 C++ 中为协议回退进行实例交换。

Listing 5.18. Instance swapping for protocol fallback in C++.


 1 CommonData{};

RawData{};

3 视频{};



5 class Connection{

public:

7 bool lowLatency(); 网络状况良好吗?

无效 transmit(RawData);

9 };



11 class RemoteEnd {

public:

13 int protocolVersion();

};

15

协议处理程序类:

17 ProtocolBase {

protected:

19 CommonData data;

int d_version;

21 连接 连接;

public:

23 虚拟

Connection initConnection(RemoteEnd otherSide);

25 Connection getConnection() {

return connection;

27 };

虚拟无效 sendData(Video vid) {

29 connection.transmit(compress(vid));

};

31 int version() { 返回 d_version; };

CommonData getData() { 返回数据;

33 次虚拟 RawData 压缩(视频 vid) = 0;

};

35

Protocol1dot0 : public ProtocolBase {

37 public:

Protocol1dot0() {

39 d_version = 1.0;

};

41 Protocol1dot0(ProtocolBase* base) {

d_version = 1.0;

43 this->data = base->getData();

};

45 RawData 压缩(视频视频);

};

47

Protocol1dot1 : public ProtocolBase {

49 public:

Protocol1dot1() {

51 d_version = 1.1;

};

53 Protocol1dot1(ProtocolBase* base) {

d_version = 1.1;

55 this->data = base->getData();

}

57 RawData 合成ress(视频视频);

};

59

Controller {

61 ProtocolBase* 处理程序;

public:

63 void setupConnection(RemoteEnd otherSide) {

if (otherSide.protocolVersion() == 1.0) {

65 handler = new Protocol1dot0();

} else {

67 handler = new Protocol1dot1();

}

69 处理程序->initConnection(otherSide);

};

71 void sendData(Video vid) {

bool networkGood =

73 handler->getConnection().lowLatency();

if (networkGood &&

75 handler->version() == 1.0) {

fall forward

77 handler = new Protocol1dot1(handler);

} else if (!networkGood &&

79 handler->version() == 1.1) {

fall back

81 handler = new Protocol1dot0(handler);

}

83 处理程序->sendData(vid);

};

85 元 };

 1 class CommonData{};

   class RawData{};

 3 class Video{};



 5 class Connection{

   public:

 7     bool lowLatency(); // is network in good shape?

       void transmit(RawData);

 9 };



11 class RemoteEnd {

   public:

13     int protocolVersion();

   };

15

   // Protocol handler classes:

17 class ProtocolBase {

   protected:

19     CommonData  data;

       int         d_version;

21     Connection connection;

   public:

23     virtual

       Connection initConnection(RemoteEnd otherSide);

25     Connection getConnection() {

           return connection;

27     };

       virtual void sendData(Video vid) {

29         connection.transmit(compress(vid));

       };

31     int version() { return d_version; };

       CommonData getData() { return data; };

33     virtual RawData compress(Video vid) = 0;

   };

35

   class Protocol1dot0 : public ProtocolBase {

37 public:

       Protocol1dot0() {

39         d_version = 1.0;

       };

41     Protocol1dot0(ProtocolBase* base) {

           d_version = 1.0;

43         this->data = base->getData();

       };

45     RawData compress(Video vid);

   };

47

   class Protocol1dot1 : public ProtocolBase {

49 public:

       Protocol1dot1() {

51         d_version = 1.1;

       };

53     Protocol1dot1(ProtocolBase* base) {

           d_version = 1.1;

55         this->data = base->getData();

       }

57     RawData compress(Video vid);

   };

59

   class Controller {

61     ProtocolBase* handler;

   public:

63     void setupConnection(RemoteEnd otherSide) {

           if (otherSide.protocolVersion() == 1.0) {

65             handler = new Protocol1dot0();

           } else {

67             handler = new Protocol1dot1();

           }

69         handler->initConnection(otherSide);

       };

71     void sendData(Video vid) {

           bool networkGood =

73             handler->getConnection().lowLatency();

           if (networkGood &&

75             handler->version() == 1.0) {

                   // fall forward

77                 handler = new Protocol1dot1(handler);

           } else if (!networkGood &&

79             handler->version() == 1.1) {

                   // fall back

81                 handler = new Protocol1dot0(handler);

           }

83         handler->sendData(vid);

       };

85 };


这是 中的每个方法中的巨大开销。我们本可以尝试将所有这些控制器 logic 放入 class 中,乍一看它似乎可以工作。但是,它要求 class 知道所有子类,这通常是相当糟糕的设计。这也意味着每当定义新协议时都必须更新。如果此类在预编译库中提供供客户端使用,则客户端无法为新的协议版本扩展该库。ControllerProtocolBaseProtocolBaseProtocolBase

This is a tremendous amount of overhead being placed in each of the methods in Controller. We could have tried to put all of this controller logic in to the ProtocolBase class, and at first glance that looks like it could work. However, it requires that the ProtocolBase class know about all of the subclasses, which is fairly poor design in general. It also means that ProtocolBase must be updated anytime a new protocol is defined. If this class is shipped in a precompiled library for clients to use, then the clients cannot extend the library for new protocol versions.

此外,这种方法不容易允许在实际实现中重用代码。如果协议 1.1 只是协议 1.0 的一个小调整,那么如果我们可以重用 1.0 的行为就好了,这可能是通过将公共代码提升到基类并允许从每个子类调用它来完成的。这种方法可能适用于两个子类。然而,一旦我们开始接触更多的子类、协议版本和特殊情况,它可能会变得很麻烦。可定义代码的公共核心的可能性会迅速下降。

In addition, this approach doesn’t easily allow for code reuse for the actual implementation. If Protocol 1.1 is just a small tweak off of Protocol 1.0, then it would be nice if we could reuse 1.0’s behavior, which would probably be done by hoisting the common code into the base class and allowing it to be called from each subclass. This approach is likely to work well for two subclasses. Once we start getting into more subclasses, protocol versions, and special cases, however, it is likely to become cumbersome. The likelihood of a common core of code being definable goes down quickly.

很快就会发现,这种方法并没有很好地支持 Grace Dynamic fallback 和 fallfoward。

It fast becomes apparent that graceful dynamic fallback and fallfoward is not well supported by this approach.

相反,我们可以让每个新的协议版本都来自先前的协议版本子类,这样只需要对协议进行必要的更改。这让我们在第一次找出每一端可能能够支持的最佳协议时只有一个类需要处理,但我们仍然需要能够恢复到以前的版本。图 5.2 显示了这种方法的扩展变体,包括多个版本。

Instead, we can have each new protocol version subclass from the prior protocol version so that only the necessary changes to the protocol need to be implemented. This lets us have only a single class to deal with when we first figure out the best possible protocol that each end might be able to support, but we still need to be able to revert to the previous version. Figure 5.2 shows an extended variant of this approach, including several versions.

图像

图 5.2.子类化方法

Figure 5.2. Subclassing approach

这种方法的一个例子在 Listing 5.19 中展示,只有两个 class。支持的类如 Listing 5.18 所示。在这种情况下,我们可以只实例化链中最后一个类的对象,如果需要,代码中的条件语句会将协议处理传递回链的适当版本。这极大地简化了支持此协议的代码的进一步维护,因为每当库的新更新出现时,只需要将协议处理对象的实例更改为最新版本,因此所有正常降级都会自动处理。

An example of this approach is shown in Listing 5.19 with just two classes. The supporting classes are as in Listing 5.18. In this case, we can instantiate an object of just the last class in the chain, and conditional statements in the code will pass the protocol handling back up the chain to the appropriate version if needed. This vastly simplifies further maintenance of the code that supports this protocol because only the instance(s) of the protocol-handling object need to be changed to the latest version anytime a new update to the library comes out, and all the graceful degradation is therefore handled automatically.

清单 5.19.使用 Revert Method 自动回退/前进。

Listing 5.19. Auto fallback/forward using Revert Method.


 1 class Protocol1dot0 {

public:

3 virtual

void initConnection(RemoteEnd otherSide) {

5 connection = otherSide.connect(1.0);

};

7 virtual

void sendData(Video vid) {

9 RawData rawData = compress(vid);

connection.transmit(原始数据);

11 };

protected:

13 个虚拟

RawData 压缩(视频);

15 连接连接;

CommonData 数据;

17 };



19 class Protocol1dot1 : public Protocol1dot0 {

public:

21 virtual

void initConnection(RemoteEnd otherSide) {

23 connection = otherSide.connect(1.1);

};

25 virtual

void sendData(Video vid) {

27 RawData rawData;

if (connection.lowLatency()) {

29 rawData = compress(vid);

} else {

31 rawData = Protocol1dot0::compress(vid);

^--- 还原方法

33 }

connection.transmit(rawData);

35 };

protected:

37 个虚拟

RawData 压缩(视频);

39 元 };

 1 class Protocol1dot0 {

   public:

 3     virtual

       void initConnection(RemoteEnd otherSide) {

 5         connection = otherSide.connect(1.0);

       };

 7     virtual

       void sendData(Video vid) {

 9         RawData rawData = compress(vid);

           connection.transmit(rawData);

11     };

   protected:

13     virtual

       RawData compress(Video);

15     Connection connection;

       CommonData data;

17 };



19 class Protocol1dot1 : public Protocol1dot0 {

   public:

21     virtual

       void initConnection(RemoteEnd otherSide) {

23         connection = otherSide.connect(1.1);

       };

25     virtual

       void sendData(Video vid) {

27         RawData rawData;

           if (connection.lowLatency()) {

29             rawData = compress(vid);

           } else {

31             rawData = Protocol1dot0::compress(vid);

               //        ^--- Revert Method

33         }

           connection.transmit(rawData);

35     };

   protected:

37     virtual

       RawData compress(Video);

39 };


现在我们已经消除了对 and 类的需求。任何客户端都只需要实例化它能够使用的最新版本,任何回退或回退都会自动发生。代码相当简洁,更直接地表达了开发人员的意图。ControllerProtocolBase

Now we have eliminated the need for the Controller and ProtocolBase classes. Any client only needs to instantiate the newest version that it is capable of working with, and any fallback or fallforward happens automatically. The code is considerably cleaner and more directly expresses the intent of the developer.

适用性

在以下情况下使用 Revert 方法

Use Revert Method when:

• 类希望重新启用超类对已覆盖的方法的实现,以便使用原始行为。

• A class wishes to reenable a superclass’s implementation of a method that it has overridden in order to use the original behavior.

结构
图像
参与者

原始行为

OriginalBehavior

至少定义方法 operation2 的基类。

A base class defining at least the method operation2.

操作

operation

operation 是进行调用的方法,并且至少在类中定义。RevertedBehavior

operation is the method in which the call takes place and is defined in at least the RevertedBehavior class.

操作 2

operation2

operation2是正在调用的方法,并且至少在类中定义。If 还必须在 的子类中定义,无论是在类层次结构中它们之间定义的类中,还是在它们之间定义的类中定义。第二个定义掩盖了 implementation in 中的默认值,并强制选择 reverted implementation。OriginalBehaviorOriginalBehaviorRevertedBehaviorOriginalBehaviorRevertedBehavior

operation2 is the method being called and is defined in at least the OriginalBehavior class. If must also be defined in a subclass of OriginalBehavior, either in RevertedBehavior or in a class defined between them in the class hierarchy. This second definition masks the implementation in OriginalBehavior from being the default within RevertedBehavior and forcing the selection of the reverted implementation.

还原行为

RevertedBehavior

的子类 ,定义了 operation 并重写了 operation2operation 调用 operation2 的实现,而我们通常希望它调用自己的该方法的实现。OriginalBehaviorOriginal-Behavior

A subclass of OriginalBehavior, with operation defined and operation2 overridden. operation calls the Original-Behavior implementation of operation2 when we would normally expect it to call its own implementation of that method.

合作

在大多数情况下,子类会覆盖父类的方法以替换其功能,但这两个方法定义可以协同工作以允许行为的扩展。 依赖于核心实现。RevertedBehaviorOriginalBehavior

In most cases, a subclass overrides a parent class’s method to replace its functionality, but the two method definitions can work together to allow an extension of the behavior. RevertedBehavior relies on OriginalBehavior for a core implementation.

后果

重写基类的方法与使用相同方法之间存在概念上的脱节,这可能会使一些学生和实践者感到困惑。覆盖方法不会擦除旧方法;它只是对子类的对象从 public view 中隐藏了旧方法。该对象仍然具有其父级方法的知识,并且可以在内部调用它们,而无需将此知识公开给外部世界。

There is a conceptual disconnect between the overriding of a base class’s method and the utilization of that same method that can be confusing to some students and practitioners. Overriding a method does not erase the old method; it merely hides the old method from public view for objects of the subclass. The object still has knowledge of its parent’s methods and can invoke them internally without exposing this knowledge to the external world.

Revert Method 是一种 Conglomeration 形式,其中添加了超类实现的知识和直接访问它的能力。

Revert Method is a form of Conglomeration to which the knowledge of a superclass’s implementation and the ability to access it directly have been added.

实现

在 C# 中:

In C#:


 1 public class OriginalBehavior {

operation()

的可选定义 3 public virtual void operation() {};

public virtual void operation2() {};

5 };



7 public class RevertedBehavior : OriginalBehavior {

public override void operation() {

9 optional work priot

if (oldBehaviorNeeded) {

11 OriginalBehavior::operation2();

} else {

13 operation2();

}

15 之后

的可选工作 };

17 如果未覆盖此项,

无需还原为超类的

版本 19 public override void operation2();

   };

 1 public class OriginalBehavior {

       // Optional definition of operation()

 3     public virtual void operation() {};

       public virtual void operation2() {};

 5 };



 7 public class RevertedBehavior : OriginalBehavior {

       public override void operation() {

 9         // Optional work priot

           if (oldBehaviorNeeded) {

11             OriginalBehavior::operation2();

           } else {

13             operation2();

           }

15         // Optional work after

       };

17     // If this were not overridden, there would be

       // no need to revert to superclass's version

19     public override void operation2();

   };


相关模式

如前所述,Revert MethodConglomeration 密切相关,不同之处在于它使用超类型的 implementation of the method to be conglomerated。如果要调用类似的方法,那么您有一个 Extend Method 的示例。如果行为是由 定义的超类型的其他对象提供的,那么这就是 Trusted Delegation 的实例。OriginalBehavior

As noted earlier, Revert Method is closely related to Conglomeration except that it uses a supertype’s implementation of the method to be conglomerated. If the similar method is to be called, then you have an example of Extend Method. If the behavior is being provided by the other objects of the supertype defined by OriginalBehavior, then that is an instance of Trusted Delegation.

方法调用分类

对象:类似

Object: Similar

对象类型:

Object Type: Subtype

方法:不同

Method: Dissimilar

图像

扩展方法

Extend Method

对象行为

Object Behavioral

意图

在重用现有代码时,补充而不是替换超类的方法中的行为。

Supplement, not replace, behavior in a method of a superclass while reusing existing code.

也称为

扩展 Super

Extending Super

赋予动机

软件生产和维护中最常见的两项任务是修复错误或添加新功能。最直接的方法是直接更改代码。如果存在 bug,请修复它。在需要新功能的地方,添加它。

Two of the most common tasks in software production and maintenance are to fix bugs or add new features. The most straightforward way of doing so is to alter the code directly. Where the bug exists, fix it. Where the new feature is needed, add it.

然而,这并不总是可能的。通常,需要修复或增强的代码无法以源代码形式提供,因为它已作为库提供。其他时候,必须保留原始代码和行为以供旧代码使用。无论哪种情况,有时都无法更改原始方法。

This isn’t always possible, however. Often the code that needs fixing or enhancing is not available in source form because it has been provided as a library. Other times, the original code and behavior must be preserved for use by legacy code. In either case, there will be times when the original method cannot be changed.

当然,如果原始源代码可用,我们可以将旧代码复制并粘贴到新方法中。但是,正如 Inheritance 规范中所示,这带来了许多问题,包括方法的不一致,并导致潜在的维护泥潭。一种不太容易出错的方法是仅在单个点定义任何实现,然后重用现有代码,根据需要调整输入或结果。在将数据传递到原始 implementation之前,可以对数据进行处理,或者可以使用原始 implementation的结果来执行其他步骤。遵循此原则是降低维护复杂性并生成清晰代码的简单方法。

If the original source is available, of course, we can just copy and paste the old code into the new method. As shown in the specification for Inheritance, however, this presents a host of problems, including inconsistency of methods, and results in a potential maintenance morass. A much less error-prone approach is to have any implementation defined at only a single point and then reuse the existing code, tweaking the input or results as needed. Data can be massaged before being passed to the original implementation, or the results of the original implementation can be used to perform additional steps. Adhering to this principle is a simple way to reduce maintenance complexity and produce clear code.

如果原始源代码不可用,那么我们在任何情况下都无法复制和粘贴代码,并且必须找到另一种方法来重用现有代码。当然,可以创建对具有原始行为的委托对象的引用,并在需要时调用它。这是 Redirection 中采用的方法,但这种方法在某些情况下很快就会失效,如程序清单 5.20 所示。如果添加的行为需要访问被原始代码封装为 private 的数据,并且代码不会将其公开供外部客户端使用或操作,则这种策略根本不可能。即使有可能,也可能不清楚新代码在做什么,并且它与原始代码的关系非常密切。

If the original source is not available, then we’re prevented from copying and pasting code in any case and have to find another way to reuse the existing code. It is possible, of course, to create a reference to a delegate object with the original behavior and call into it when needed. This is the approach taken in Redirection, but this approach breaks down quickly in certain cases, as shown in Listing 5.20. If the added behavior needs to have access to data that was encapsulated as private by the original code, and the code does not expose it for use or manipulation by outside clients, then this tactic simply isn’t possible. Even when it is possible, it may not be clear what the new code is doing and that its relationship to the original code is quite close.

清单 5.20.在 Python 中使用重定向来添加行为。

Listing 5.20. Using Redirection in Python to add behavior.


   OriginalBehavior:

2 __privateData = True

# 为私有属性

预置 __ 4 def desiredBehavior(self, data):

# 做一些有趣的

事情 6 返回数据



8 class NewBehavior:

def addMoreBehavior(self, data):

10 # 执行任何必要的通话前设置

# 无法访问 ob

的__privateData 12 ob = OriginalBehavior()

val = ob.desiredBehavior(data)

14 # 通话后清理或更多功能

   class OriginalBehavior:

 2     __privateData = True

       # Prepend __ for a private attribute

 4     def desiredBehavior(self, data):

           # Do something interesting

 6         return data



 8 class NewBehavior:

       def addMoreBehavior(self, data):

10         # Perform any necessary pre-call setup

           # Cannot access __privateData of ob

12         ob = OriginalBehavior()

           val = ob.desiredBehavior(data)

14         # Post-call cleanup or more functionality


这些问题的解决方案是使用 Extend Method,如 Listing 5.21 所示。我们使用 Inheritance 实现一个新类,该类从包含原始所需行为的类中子类化,因为它提供了重用原始代码的机制。然后我们覆盖原始方法,添加我们需要的方法的实现,但是当我们需要执行其行为时,请回调超类的方法实现。这为我们提供了对更改行为的简单维护、重用和封装。

The solution to these problems is to use Extend Method, as in Listing 5.21. We implement a new class that subclasses off of the class containing the original desired behavior using Inheritance because it provides the mechanisms for reuse of the original code. We then override the original method, adding what implementation of the method we need, but make a call back to the superclass’s implementation of the method when we need its behavior to be executed. This provides us with simple maintenance, reuse, and encapsulation of the altered behavior.

清单 5.21.使用 Extend Method 添加行为。

Listing 5.21. Using Extend Method to add behavior.


   OriginalBehavior:

2 __privateData = True

# 为私有属性

预置 __ 4 def desiredBehavior(self, data):

# 做一些有趣的

事情 6 返回数据



8 class NewBehavior(OriginalBehavior):

def desiredBehavior(self, data):

10 # 执行任何必要的呼叫前设置

# 可以访问__privateData!

12 superDelegate = super(NewBehavior, self)

superDelegate.desiredBehavior(data)

14 # ^--- 扩展方法

# 调用后清理或更多功能

   class OriginalBehavior:

 2     __privateData = True

       # Prepend __ for a private attribute

 4     def desiredBehavior(self, data):

           # Do something interesting

 6         return data



 8 class NewBehavior(OriginalBehavior):

       def desiredBehavior(self, data):

10         # Perform any necessary pre-call setup

           # Can access __privateData!

12         superDelegate = super(NewBehavior, self)

           superDelegate.desiredBehavior(data)

14         # ^--- Extend Method

           # Post-call cleanup or more functionality


适用性

在以下情况下使用 Extend Method

Use Extend Method when:

• 需要扩展方法的现有行为,但不能替换该方法的现有行为。

• Existing behavior of a method needs to be extended but not replaced.

• 由于缺少源代码,代码重用是首选或必要的。

• Reuse of code is preferred or necessitated by lack of source code.

• 使用 Redirection 是不可能的,也不是最佳选择,因为新行为需要访问原始实现专用的数据。

• Using Redirection is not possible or optimal because the new behavior needs access to data that is private to the original implementation.

结构
图像
参与者

原始行为

Original Behavior

定义接口并包含具有所需核心功能的方法操作的实现。

Defines interface and contains an implementation of the method operation with the desired core functionality.

操作

operation

定义者 以提供基本行为。overrided in 以提供使用原始实现的新实现。OriginalBehaviorExtendedBehavior

Defined by OriginalBehavior to provide basic behavior. Overridden in ExtendedBehavior to provide a new implementation that makes use of the original implementation.

扩展行为

Extended Behavior

使用 的接口 ,然后重新实现操作方法,以包括对基类代码的调用,该代码被添加的代码和/或行为包围。OriginalBehavior

Uses interface of OriginalBehavior, then reimplements the operation method to include a call to the base class code surrounded by added code and/or behavior.

合作

在大多数情况下,当子类覆盖父类的方法时,目的是替换该原始方法的功能。但是,这两个方法定义可以协同工作,以允许扩展父类的行为,而不是替换。 依赖于 interface 和 core 实现。ExtendedBehaviorOriginalBehavior

In most cases when a subclass overrides a parent class’s method, the purpose is to replace the functionality of that original method. The two method definitions, however, can work together to allow an extension of the behavior of the parent class instead of a replacement. ExtendedBehavior relies on OriginalBehavior for both interface and core implementation.

后果

Revert Method 一样,调用方法的重写版本的概念可能会令人困惑。在 Extend Method 中,它可能更加令人困惑,因为调用方法似乎正在调用一个 ghost 方法。使用这种模式可以优化代码重用,但 in 方法变得有些脆弱 — 它的行为现在依赖于 to 随着时间的推移而保持不变。行为以多态方式扩展,并且对 的客户端透明。operationOriginalBehaviorExtendedBehavior::OperationOriginalBehavior

As with Revert Method, the concept of calling an overridden version of a method can be confusing. In Extend Method, it can be even more confusing because the calling method seems to be invoking a ghost method. Using this pattern optimizes code reuse, but the method operation in OriginalBehavior becomes somewhat fragile—its behavior is now relied on by ExtendedBehavior::Operation to be invariant over time. Behavior is extended polymorphically and transparently to clients of OriginalBehavior.

实现

在 Java 中:

In Java:


 1 public class OriginalBehavior {

public void operation() {

3 Do 核心行为

};

5 };

public class ExtendedBehavior

7 extends OriginalBehavior {

public void operation() {

9 optional work before

super.operation();

11 可选工作

};

13 };

 1 public class OriginalBehavior {

       public void operation() {

 3         // Do core behavior

       };

 5 };

   public class ExtendedBehavior

 7   extends OriginalBehavior {

       public void operation() {

 9         // Optional work before

           super.operation();

11         // Optional work after

       };

13 };


在 C++ 中:

In C++:


 1 class OriginalBehavior {

public:

3 virtual void operation() {

Do core behavior

5 };

};

7 class ExtendedBehavior : public OriginalBehavior {

public:

9 void operation() {

11 之前的

可选工作 OriginalBehavior::operation();

13 岁以后

的可选工作     };

   };

 1 class OriginalBehavior {

   public:

 3     virtual void operation() {

           // Do core behavior

 5     };

   };

 7 class ExtendedBehavior : public OriginalBehavior {

   public:

 9     void operation() {

          // Optional work before

11        OriginalBehavior::operation();

          // Optional work after

13     };

   };


相关模式

Extend MethodRecursion 一样,是使用单个对象定义的。将此对象相似性转换为不同的不同对象会导致 Trusted Redirection 实例,该实例使用多态性遍历受信任的相关类群集以实现功能。放宽方法相似性以调用超类的不同方法会导致 Revert Method。因为 Revert MethodConglomeration 的超类感知变体,所以 Extend MethodRecursion 的超类感知版本,即使结果的行为完全不同。只需删除该超类访问并调用当前类型的实现即可实现递归

Extend Method, like Recursion, is defined using a single object. Converting this object similarity to distinct dissimilar objects results in an instance of Trusted Redirection, which uses polymorphism to traverse a cluster of trusted related classes for functionality. Relaxing the method similarity to call a different method of the superclass results in Revert Method. Because Revert Method is a superclass-aware variation of Conglomeration, Extend Method is a superclass-aware version of Recursion, even though the resulting behavior is quite different. Recursion can be reached by simply removing that superclass access and invoking the current type’s implementation.

方法调用分类

对象:类似

Object: Similar

对象类型:

Object Type: Super

方法:类似

Method: Similar

图像

委托集团

Delegated Conglomeration

对象行为

Object Behavioral

意图

当多个相同类型的对象必须协同工作才能完成一项任务时,每个对象执行不同的行为。

When multiple objects of the same type must work in concert to complete a task, with each performing different behaviors.

赋予动机

相同类型的对象通常协同工作以执行任务。具有异构行为的同构数据环境通常被编码为类似对象的集合,这些对象相互协作以产生更大的功能。

Objects of the same type often work in concert to perform tasks. Homogeneous data environments with heterogeneous behavior are frequently coded as a collection of like objects collaborating to produce a larger functionality.

清单 5.22 展示了一个使用社交网站的例子。每个用户的帐户都有一个好友列表,用户可能想要做的一个操作是邀请其他朋友参加活动。用户可以设置自己的通知首选项,包括他们希望在收到邀请时联系的电子邮件地址。

Listing 5.22 presents an example using a social networking site. Each user’s account has a list of friends, and one action users are likely to want to do is invite other friends to an event. Users can set their own notification preferences, including the email address they want to be contacted at when they get an invitation.

清单 5.22.用 Java 天真地邀请朋友。

Listing 5.22. Inviting friends naively in Java.


   导入 java.util.Vector;

2 import java.lang.String;



4 public class UserAccount {

Vector<UserAccount> allMyFriends;

6 public String notificationEmail;

8 public void inviteToEvent (

Vector<UserAccount> friends, String msg) {

10 for (UserAccount friend : friends) {

String email = friend.notificationEmail;

12 发送电子邮件

}

14     }

   };

   import java.util.Vector;

 2 import java.lang.String;



 4 public class UserAccount {

       Vector<UserAccount> allMyFriends;

 6     public String       notificationEmail;

 8     public void inviteToEvent (

             Vector<UserAccount> friends, String msg) {

10         for (UserAccount friend : friends) {

               String email = friend.notificationEmail;

12             // send email

           }

14     }

   };


此解决方案有效,但它要求每个账户公开其电子邮件信息。大多数用户不想在社交网站上公开他们的电子邮件地址。为了使这些信息更加私密,活动策划者可以向朋友请求电子邮件,如果朋友愿意,他们可以提供这些电子邮件,如程序清单 5.23 所示。

This solution works, but it requires each account to expose its email information publicly. Most users do not want to expose their email address on a networking site. To keep this information slightly more private, event planners can request emails from friends, and friends can provide those emails if they wish to, as shown in Listing 5.23.

清单 5.23.邀请朋友的方法稍微好一些。

Listing 5.23. A slightly better approach for inviting friends.


 1 import java.util.Vector;

导入 java.lang.String;



3 public class UserAccount {

5 Vector<UserAccount> allMyFriends;

public String notificationEmail;



7 public void inviteToEvent (

9 Vector<UserAccount> friends, String msg) {

for (UserAccount friend : friends) {

11 String email = friend.getEmail(this);



发送电子邮件 13 }

}

15

public String getEmail (UserAccount requester) {

17 String returnVal = “”;

if (allMyFriends.contains(requester)) {

19 returnVal = notificationEmail;

}

21 return returnVal;

};

23 };

 1 import java.util.Vector;

   import java.lang.String;

 3

   public class UserAccount {

 5     Vector<UserAccount> allMyFriends;

       public String       notificationEmail;

 7

       public void inviteToEvent (

 9           Vector<UserAccount> friends, String msg) {

           for (UserAccount friend : friends) {

11             String email = friend.getEmail(this);

               // send email

13         }

       }

15

       public String getEmail (UserAccount requester) {

17         String returnVal = "";

           if (allMyFriends.contains(requester)) {

19             returnVal = notificationEmail;

           }

21         return returnVal;

       };

23 };


这种方法仍然要求用户向好友列表中请求该地址的任何人提供他们的电子邮件地址。同样,大多数用户可能只希望收到邀请,而不必分享他们的电子邮件地址。通过使用 Delegated Conglomeration 来,可以将电子邮件地址的可见性与接收邀请的能力分开,如示例 5.24 所示。现在,当用户邀请朋友时,他们不会直接查看或访问朋友的电子邮件信息。相反,他们会向好友的账户发送通知请求,以确保在不暴露电子邮件地址的情况下发送邀请。

This approach still requires users to provide their email address to anyone on their friends list who requests it. Again, most users would probably just like to receive an invitation without having to share their email address. Visibility of an email address can be separated from the capability to receive invitations by use of Delegated Conglomeration, as in Listing 5.24. Now when users invite friends, they don’t see or access their friends’ email information directly. Instead, they send a notification request to their friends’ accounts, which ensures the invitation is delivered without exposing the email addresses.

清单 5.24.Java 中的委托聚合

Listing 5.24. Delegated Conglomeration in Java.


 1 import java.util.Vector;

导入 java.lang.String;



3 public class UserAccount {

5 Vector<UserAccount> allMyFriends;

public String notificationEmail;



7 public void inviteToEvent (

9 Vector<UserAccount> friends, String msg) {

for (UserAccount friend : friends) {

11 friend.notify(this, msg);

^--- 委托集合

13 }

}

15

public boolean notify (UserAccount inviter,17

String msg) {

发送电子邮件至 notificationEmail

19 return true;

};

21 };

 1 import java.util.Vector;

   import java.lang.String;

 3

   public class UserAccount {

 5     Vector<UserAccount> allMyFriends;

       public String       notificationEmail;

 7

       public void inviteToEvent (

 9           Vector<UserAccount> friends, String msg) {

           for (UserAccount friend : friends) {

11             friend.notify(this, msg);

               // ^--- Delegated Conglomeration

13         }

       }

15

       public boolean notify (UserAccount inviter,

17                           String msg) {

           // Send email to notificationEmail

19         return true;

       };

21 };


清单 5.235.24 在技术上是 Delegated Conglomeration 的实例,但后者显示了更好的理由和更清晰的用例。Listing 5.23 根据请求者有选择地公开私有数据;清单 5.24 显示了 Delegated Conglomeration 可以用来完全隐藏私有数据,这样它就永远不会被共享。第二个对象被赋予了执行使用私有数据的任务的唯一责任。

Listings 5.23 and 5.24 are technically instances of Delegated Conglomeration, but the latter shows a much better justification and clearer use case. Listing 5.23 selectively exposes private data based on who requests it; Listing 5.24 shows that Delegated Conglomeration can be used to completely hide private data such that it is never shared. The second object is given the sole responsibility to perform a task with private data.

适用性

在以下情况下使用 Delegated Conglomeration

Use Delegated Conglomeration when:

• 任务可以分解为由同一对象类型正确处理的子任务。

• A task can be broken into subtasks that are properly handled by the same object type.

• 许多相同类型的对象协同工作以完成任务。

• Many objects of the same type work in concert to complete a task.

• 单个对象无法单独完成任务。

• A single object cannot complete the task alone.

• 该任务需要其他对象保持私有的数据。

• The task requires data that is kept private by the other objects.

结构
图像
参与者

委托人

Delegator

包含对自身类型的其他实例的引用的对象类型。

The object type that contains references to other instances of its own type.

委托目标

delegateTarget

为执行任务而调用的封闭实例。

The enclosed instance that is called upon to perform a task.

操作

operation

第一个对象中的调用点。

The calling point within the first object.

操作 2

operation2

第二个对象要完成的 subtask。

The subtask to be completed by the second object.

合作

此模式只涉及一种对象类型 Delegator,但该类型有两个不同的实例。一个对象依赖于另一个对象来执行任务的某些部分,就像 Delegation 一样,并且该 subtask 不直接与当前请求关联,就像 Conglomeration 一样。

Only one object type, Delegator, is involved in this pattern, but there are two distinct instances of that type. One object relies on the other to perform some part of the task, as with Delegation, and that subtask is not directly associated with the current request, as with Conglomeration.

后果

Redirected Recursion 一样,此模式提供了在多个对象之间分配任务的能力。然而,与它的表亲不同的是,Delegated Conglomeration 提供了一个明确的决策点,将行为决定逻辑与行为实现分开。

As with Redirected Recursion, this pattern offers the ability to distribute tasks among a number of objects. Unlike its cousin, however, Delegated Conglomeration offers a clear decision-making point, separating the behavior-determining logic from the behavior implementation.

实现

在 C++ 中:

In C++:


 1 class Delegator {

Delegator* delegateTarget;

3 public:

void operation() {

5 可选工作 prior

delegateTarget->operation2();

7 } 后

可选工作;

9 void operation2() {

做点有用的

事情 11     };

   }

 1 class Delegator {

       Delegator* delegateTarget;

 3 public:

       void operation() {

 5         // Optional work prior

           delegateTarget->operation2();

 7         // Optional work after

       };

 9     void operation2() {

           // Do something useful

11     };

   }


在 Objective-C 中:

In Objective-C:


   @interface委托人

2 {

Delegator* delegateTarget;

4 }

- (void) 操作;

6 - (无效) 操作2;



@end 8

@implementation Delegator

10 - (void) operation

{

12 可选工作

[delegateTarget operation2];

14 16 之后的可选工作

}

16 - (void) 操作2

{

18 做一些有用的

事情 }

20 @end

   @interface Delegator

 2 {

       Delegator* delegateTarget;

 4 }

   - (void) operation;

 6 - (void) operation2;

   @end

 8

   @implementation Delegator

10 - (void) operation

   {

12     // Optional work prior

       [delegateTarget operation2];

14     // Optional work after

   }

16 - (void) operation2

   {

18     // Do something useful

   }

20 @end


相关模式

显然,Delegated Conglomeration 可以通过放松对象之间的类型关系转换为 Delegation,也可以通过让对象回调到自身来转换为 Conglomeration。如果类型相似性更改为子类型化,则意味着对象正在调用一系列受信任的类,从而导致受信任的委派。保持类型相似性相同并保留不同的对象,但让方法调用另一个对象中的相似对应项以调用相同的行为,这就带来了重定向递归

Obviously, Delegated Conglomeration can be converted either to Delegation, by relaxing the typing relationship between the objects, or to Conglomeration, by having the object call back into itself. If the type similarity is changed to subtyping, it means the object is calling into a family of trusted classes, resulting in Trusted Delegation. Keeping the type similarity the same and retaining the distinct objects, but instead having the method call into its similar counterpart in the other object to invoke the same behavior, brings us to Redirected Recursion.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:类似

Object Type: Similar

方法:不同

Method: Dissimilar

图像

重定向递归

Redirected Recursion

对象行为

Object Behavioral

意图

对多个对象执行单个操作,并让这些对象负责该操作的分发和调用。

To perform a singular action on multiple objects and have the objects be responsible for distribution and invocation of that action.

赋予动机

通常,我们希望对系统中的大量数据执行相同的操作。有时,这些数据最好由外部容器组织,并且数据可以被视为“哑”,对其自己的组织不承担任何责任;它仅由遍历容器的外部实体执行操作。迭代容器并对其中包含的每个项执行操作是此过程的一个示例。它要求从外部强加数据的结构,并且行为的调用流也由外部控制。Listing 5.25 提供了一个简单的例子。

Often we wish to perform the same action on a lot of data in a system. Sometimes that data is best organized by an external container, and the data can be considered “dumb,” with no responsibility for its own organization; it is acted on only by an external entity traversing the container. Iterating over a container and performing an action on each item contained in it is an example of this process. It requires that the structure of the data be imposed from outside and that the flow of invocation of the behavior also be controlled externally. A simple example is provided in Listing 5.25.

清单 5.25.C 语言中的传统迭代和调用。

Listing 5.25. Traditional iteration and invocation in C.


  #include <stdio.h>

2 #define LENGTH 4

int data[LENGTH] = {1, 2, 3, 4};

4 void printAll() {

int i;

6 for(i = 0; i < LENGTH; i++) {

printf(“%d\n”, data[i]);

8     }

  };

  #include <stdio.h>

2 #define LENGTH 4

  int data[LENGTH] = {1, 2, 3, 4};

4 void printAll() {

      int i;

6     for(i = 0; i < LENGTH; i++) {

         printf("%d\n", data[i]);

8     }

  };


此解决方案是解决原始问题的常用方法,但可以对其进行改进。首先,面向对象系统中的数据不必是愚蠢的。它可以在请求时执行自己的操作。相反,我们可以做一些更像 Listing 5.26 中所示的事情。

This solution is a common way of solving the original problem, but it can be improved on. First, data in an object-oriented system doesn’t have to be dumb. It can perform its own actions when requested. We can instead do something more like what is shown in Listing 5.26.

清单 5.26.C++ 中面向对象的迭代和调用。

Listing 5.26. Object-oriented iteration and invocation in C++.


 1 #include <cstdio>



3 class DataItem {

int data;

5 public:

DataItem(int val) : data(val) {};

7 void print() {

printf(“%d\n”, data);

9 };

};

11

#define LENGTH 4

13 DataItem data[LENGTH] =

{DataItem(1), DataItem(2),

15 DataItem(3), DataItem(4)};



17 void printAll() {

int i;

19 for(i = 0; i < LENGTH; i++) {

data[i].print();

21     }

   };

 1 #include <cstdio>



 3 class DataItem {

       int data;

 5 public:

       DataItem(int val) : data(val) {};

 7     void print() {

           printf("%d\n", data);

 9     };

   };

11

   #define LENGTH 4

13 DataItem data[LENGTH] =

       {DataItem(1), DataItem(2),

15      DataItem(3), DataItem(4)};



17 void printAll() {

       int i;

19     for(i = 0; i < LENGTH; i++) {

           data[i].print();

21     }

   };


现在数据负责进行打印。该函数不必了解数据、数据如何编码,甚至不需要了解数据如何打印。它只需要知道如何遍历组织容器并调用每个对象。printAll()print()

Now the data is responsible for doing the printing. The printAll() function doesn’t have to know anything about the data, how it is encoded, or even how it is printed. It just has to know how to traverse the organizing container and invoke print() on each object.

这个解决方案更好,但我们可以更进一步。数据最好用图形或平衡树来表示,而不是数组。就目前而言,任何希望对上述数组中的每个元素执行操作的代码都必须修改,才能遍历新的组织结构。在大型系统中,这可能是一项艰巨的任务。但是,因为对象可以通过使用 Retrieve 包含对其他对象的引用,所以我们可以让对象也处理它们自己的组织,这让我们想到了类似 Listing 5.27 的东西。

This solution is better, but we can take it a step further. Instead of an array, the data may be best represented by a graph or a balanced tree. As it currently stands, any piece of code that wishes to perform an operation on each element in the above array would have to be modified to now traverse the new organizational structure. In a large system, this can be a huge undertaking. Because objects can contain references to other objects through the use of Retrieve, however, we can have the objects also take care of their own organization, leading us to something like Listing 5.27.

清单 5.27.C++ 中的基本重定向递归

Listing 5.27. Basic Redirected Recursion in C++.


   #include <cstdio>

2

class DataItem {

4 int data;

dataItem* next;

6 public:

DataItem(int val) : data(val), next(NULL) {};

8 DataItem(int val, DataItem* next) :

data(val), next(next) {};

10 void print() {

printf(“%d\n”, data);

12 if (next) {

next->print();

14 ^--- 重定向递归

}

16 };

};

18

个 DataItem 数据 =

20 {DataItem(1,

new DataItem(2,22

new DataItem(3,

new DataItem(4))))};

24

void printAll() {

26 data.print();

   };

   #include <cstdio>

 2

   class DataItem {

 4     int data;

       DataItem* next;

 6 public:

       DataItem(int val) : data(val), next(NULL) {};

 8     DataItem(int val, DataItem* next) :

           data(val), next(next) {};

10     void print() {

           printf("%d\n", data);

12         if (next) {

               next->print();

14             // ^--- Redirected Recursion

           }

16     };

   };

18

   DataItem data =

20     {DataItem(1,

           new DataItem(2,

22             new DataItem(3,

                   new DataItem(4))))};

24

   void printAll() {

26     data.print();

   };


现在,不仅负责打印行为,还负责如何查找下一条数据,都已移交给数据对象。由于数据现在负责自己的组织,因此它也可以负责自己的调用排序。如果类决定更改其实现并将其伙伴对象存储为红黑树、hashmap 或任何其他形式的数据结构,则可以自由地执行此操作,并且不需要更改代码。这并不总是正确的方法,但在适当的情况下,当系统正在处理的对象可以被认为是自组织的,并且只需要一个触发器来串联启动复杂的事件序列时,它可能非常强大。DataItemprintAll()

Now responsibility not only for the printing behavior but also for how to find the next piece of data has been handed over to the data object. Because the data is now responsible for its own organization, it can also be responsible for its own invocation ordering. If the DataItem class decides to change its implementation and store its partner objects as a red-black tree, a hashmap, or any other manner of data structure, it is free to do so, and the code in printAll() doesn’t need to change. This isn’t always the right approach to take, but it can be powerful in the right circumstances when objects that a system is dealing with can be trusted to be self-organizing and only need a trigger to initiate a complex sequence of events in tandem.

例如,想象一下,一排伞兵准备跳伞。空间狭小,因此指挥官不能沿着线走来示意每个士兵单独跳跃。相反,他站在队伍的后面,当下降时间到来时,他拍了拍最后一名士兵的肩膀。那个士兵知道要拍拍他前面的士兵的肩膀,并在那个士兵跳下后跳起来。这可以沿着任意长度的线路继续,从 2 到 200 名士兵。伞兵们唯一的任务是,当他们感觉到肩膀被轻拍时,轻拍排队的下一个人;等;在空间可用时向前随机播放;当他们看到前面的士兵走时,就跳到下一个。指挥官向每位士兵发布一个命令,而不是一个命令。这个代码的样例可能看起来像 Listing 5.28

Imagine, for example, a line of paratroopers getting ready for a jump. Space is tight, so the commander cannot walk down the line to indicate to each trooper to jump individually. Instead, he stands at the back of the line, and when the drop time comes, he taps the last trooper on the shoulder. That trooper knows to tap the shoulder of the trooper in front of him and to jump after that soldier has jumped. This can continue down a line of arbitrary length, from 2 to 200 troopers. The paratroopers’ only tasks are to, when they feel a tap on their shoulder, tap the next person in line; wait; shuffle forward as space is available; and when they see the soldier in front of them go, jump next. The commander issues one order instead of one to each soldier. A sample coding of this might look like Listing 5.28.

清单 5.28.实现重定向递归的伞兵。

Listing 5.28. Paratroopers implementing Redirected Recursion.


 1 伞兵 {

bool _hasJumped;

3 伞兵* nextTrooper;

public:

5 Paratrooper() : _hasJumped(false) {};

void jump() {

7 if (nextTrooper) {

nextTrooper->jump();

9 ^--- 重定向递归

while (! nextTrooper->hasJumped() ) {

11 shuffleForward();

}

13 }

leap();

15 };

void leap() {

17 _hasJumped = true;

进入地心引力的甜蜜拥抱

19 };

void shuffleForward() {

21 迈出一步

};

23 bool hasJumped() {

返回 _hasJumped;

25 };

};

27

指挥官 {

29 伞兵* backOfLine;

public:

31 void greenLight() {

backOfLine->jump();

33     };

   };

 1 class Paratrooper {

       bool            _hasJumped;

 3     Paratrooper*    nextTrooper;

   public:

 5     Paratrooper() : _hasJumped(false) {};

       void jump() {

 7        if (nextTrooper) {

              nextTrooper->jump();

 9            // ^--- Redirected Recursion

              while (! nextTrooper->hasJumped() ) {

11                shuffleForward();

              }

13        }

          leap();

15     };

       void leap() {

17         _hasJumped = true;

           // Enter gravity's sweet embrace

19     };

       void shuffleForward() {

21         // Take a step

       };

23     bool hasJumped() {

           return _hasJumped;

25     };

   };

27

   class Commander {

29     Paratrooper* backOfLine;

   public:

31     void greenLight() {

           backOfLine->jump();

33     };

   };


当前伞兵无法跳跃,直到前面的伞兵完成任务,依此类推。最终,每个伞兵都以适当的顺序完成了他或她的任务,而且几乎没有指导。订单沿链向下发布,但行为会传播回来。第一个点击的士兵是最后一个跳下的。

The current paratrooper cannot jump until the trooper in front has completed the task, and so on, and so on. Eventually each paratrooper has completed his or her task, in the proper order, and with very little instruction. The order issues down the chain, but the behavior propagates back. The first trooper tapped is the last to jump.

适用性

在以下情况下使用重定向递归

Use Redirected Recursion when:

• 递归是将任务分解为多个子部分的简洁方法。

• Recursion is a clean way to break up the task into subparts.

• 必须交互多个相同类型的对象才能完成任务。

• Multiple objects of the same type must interact to complete the task.

• 对象可以负责它们自己的组织。

• The objects can be responsible for their own organization among themselves.

结构
图像
参与者

Recursor (解析器)

Recursor

一个对象类型,它包含对自身类型的另一个实例的引用。

An object type that holds a reference to another instance of its own type.

redirectTarget

redirectTarget

封闭的实例。

The enclosed instance.

操作

operation

其中的方法本身是递归的,但通过 .RecursorredirectTarget

A method within Recursor that is recursive on itself but through redirectTarget.

合作

同一对象类型的多个实例交互以完成任务。每个实例都知道如何向下一个对象发送消息。

Multiple instances of the same object type interact to complete a task. Each instance knows how to message the next object for its turn.

后果

这是在使用大量对象时实现共享递归行为的强大方法,在许多系统中都可以找到。它允许在可以自组织的不同数据集之间拆分功能,但它将功能限制为特定行为。它类似于硬件(如图形处理器)中的单指令多数据 (SIMD) 计算,不同之处在于在这种情况下,数据负责传递指令。这可能是解决需要分而治之算法的问题的一种高度灵活的方法,因为更改方法的实现会在所有对象之间平等传播。

This is a powerful method for shared recursive behavior when using a number of objects, and it is found in many systems. It allows functionality to be split among disparate data sets that can be self-organized, but it constrains the functionality to a particular behavior. It is analogous to single instruction, multiple data (SIMD) computing in hardware such as a graphics processor, except that in this case the data is responsible for passing along the instruction. This can be a highly flexible approach to solving problems that require a divide-and-conquer algorithm, as changing the method’s implementation propagates across all objects equally.

实现

在 Java 中:

In Java:


  public 类Recursor {

2 Recursor redirectTarget;

public void operation() {

4 之前可选工作...

redirectTarget.operation()的

6 可选工作后...

};

8 };

  public class Recursor {

2     Recursor    redirectTarget;

      public void operation() {

4         // Optional work before...

          redirectTarget.operation();

6         // Optional work after...

      };

8 };


在 Python 中:

In Python:


  Recursor:

2 def __init__(self):

__redirectTarget = Recursor()

4

def operation(self):

6 # 可选工作之前...

redirectTarget.operation()的

8 # 可选工作后...

  class Recursor:

2     def __init__(self):

          __redirectTarget = Recursor()

4

      def operation(self):

6         # Optional work before...

          redirectTarget.operation();

8         # Optional work after...


相关模式

正如您从名称中所期望的那样,通过消除方法相似性要求,可以将 Redirected Recursion 转换为 Redirection。同样,将不同的对象 dissimilarity 折叠到一个对象中会导致简单的递归。保留对象 dissimilarity 以加强多个对象之间的合作,同时将方法相似性放宽到方法不相似性,从而产生 Delegated Conglomeration。最后,通过将类型关系更改为子类型化之一来实现可信重定向,这允许通过与启动调用的类相关的受信任类集合以多态方式处理方法调用。

As you might expect from the name, Redirected Recursion can be converted into Redirection by eliminating the method similarity requirement. Similarly, collapsing the distinct object dissimilarity into one object results in simple Recursion. Retaining the object dissimilarity to enforce cooperation between multiple objects while relaxing the method similarity to a method dissimilarity yields Delegated Conglomeration. Finally, Trusted Redirection is achieved by changing the type relationship to one of subtyping, which lets the method call be handled polymorphically through a trusted collection of classes related to the one initiating the call.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:类似

Object Type: Similar

方法:类似

Method: Similar

图像

受信任的委派

Trusted Delegation

对象行为

Object Behavioral

意图

相关类通常被定义为集体执行任务。在这些情况下,相关类型的多个对象可以以通用方式交互,以将任务相互委派。如果对象属于相关类型,则对象可以对它们可以放置在另一个对象中的信任级别做出某些格式正确的假设。

Related classes are often defined as such to perform tasks collectively. In these cases, multiple objects of related types can interact in generalized ways to delegate tasks to one another. If the objects are of related types, the objects can make certain well-formed assumptions about the level of trust they can place in the other object.

赋予动机

当对象必须协同工作以执行任务时(例如在 Delegation 中),它们将对另一个对象施加一定程度的信任以完成其任务。确保信任或至少降低所涉及的风险的一种方法是将调用对象知道的任务部分委托给另一个对象。共享对象信息的一种常见方法是使它们的类型相关,最简单的形式是子类型关系。

When objects must act in concert to perform a task, such as in Delegation, they are placing a certain amount of trust in the other object to complete its task. One way to ensure trust, or at least to reduce the risk involved, is to delegate the portion of the task that the calling object knows something about to another object. A common way to share information about objects is for their types to be related, and the simplest form of that is the subtyping relationship.

通过将 task 传递给当前对象的 supertype 的对象,可以提前知道接收对象的某些方面。在实现调用之前,必须建立方法签名,并且可以从方法签名之间的关系中假定意图,就像 Delegation 一样。但是,此外,可以精确地知道 intent,因为调用对象也具有相同的方法。实现可能有所不同,但调用对象继承了基本的概念意图和签名。它甚至可能具有相同的默认实现。

By passing a task to an object of a supertype of the current object, certain aspects of the receiving object can be known ahead of time. The method signatures are necessarily established before the call can be implemented, and the intent can be assumed from the relationship between the method signatures, as with Delegation. In addition, however, the intent can be known precisely, because the calling object also has that same method. The implementation may differ, but the calling object has inherited the basic conceptual intent as well as the signature. It may even have the same default implementation.

然而,意图和概念在这里是最重要的。调用对象知道委托对象至少具有超类的语义。通过多态性(如果适用),委托对象可以限制这些语义以提供专用化,但通常不允许放宽它们。调用对象知道委托对象将符合一组特定的可预确定准则。尽管这在技术上在任何强类型语言中都是正确的(因为调用对象必须知道调用站点的委托对象的类型),但该类型是调用对象自己的超类型这一事实提供了完全不同的知识级别。此附加知识使调用对象能够在委托对象中放置更高级别的信任。

Intent and concept, however, are most important here. The calling object knows that the delegate object is going to have at least the semantics of the superclass. Through polymorphism (where applicable), the delegate object may restrict these semantics to provide specialization, but in general, it is not allowed to relax them. The calling object knows that the delegate object will conform to a certain set of predeterminable guidelines. Although this is technically true in any strongly typed language—because the calling object must have an idea of the type of the delegate object at the calling site—the fact that the type is the calling object’s own supertype offers an entirely different level of knowledge. This additional knowledge enables the calling object to place a higher level of trust in the delegate object.

用户界面是一种熟悉的系统类型,可在其中查找受信任的委派和相关模式,例如代理委派受信任的重定向委派重定向。当接口和方法名称已知,但确切的对象类型(因此方法主体)已知时,此模式允许将任务分配到一系列类(通常称为类集群)中。它是一种多态委派形式,其中调用对象是多态类型之一。

User interfaces are a familiar type of system in which to find Trusted Delegation and related patterns such as Deputized Delegation, Trusted Redirection, and Deputized Redirection. This pattern allows tasks to be parceled out within a family of classes, often called a class cluster, when the interface and method name are known, but the precise object type and, therefore, method body may not be. It is a form of polymorphic delegation in which the calling object is one of the polymorphic types.

考虑一个窗口系统,该系统包括滑块条和旋转拨盘作为输入控件,文本字段和条形图作为显示小部件。输入控件与特定的显示 Widget 相关联,并在用户调整控件时向该 Widget 发送值的更新。输入控件不需要精确地知道另一端的显示小部件类型;他们只需要知道他们必须以适当的值作为参数来调用方法。因为 input 控件也必须显示一个值,所以可以通过编程方式相应地更改它们的调整,因此它们也需要一个方法。根据我们的 继承 模式,输入控件和显示小部件似乎属于同一个系列,事实上,我们想确保它们都可以交互,因此我们相应地创建了一个类层次结构,如程序清单 5.29 所示。updateValueupdateValue

Consider a windowing system that includes slider bars and rotary dials as input controls and text fields and bar graphs as display widgets. An input control is tied to a particular display widget and sends that widget updates of values when the control is adjusted by the user. The input controls don’t need to know precisely what kind of display widget is at the other end; they just need to know that they must call the updateValue method with the appropriate value as a parameter. Because input controls also necessarily display a value, it is possible to programmatically change their adjustment accordingly, so they too need an updateValue method. By our Inheritance pattern, it seems the input controls and display widgets are of the same family and, in fact, we want to make sure that they can all interact, so we create a class hierarchy accordingly, as in Listing 5.29.

清单 5.29.演示 C++ 中的受信任委派的 UI 小部件。

Listing 5.29. UI widgets demonstrating Trusted Delegation in C++.


   UIWidget {

2 public:

virtual void updateValue( int newValue );

4 };



6 InputControl : UIWidget{

UIWidget* target;

8 public:

void userHasSetNewValue(int myNewValue) {

10 target->updateValue(myNewValue);

^--- 受信任的委托

12 }

};

14

class SliderBar : public InputControl {

16 public:

相应地

移动滑块条 18 void updateValue( int newValue );

void acceptUserClick() {

20 确定新值,设置它

int newVal;

22 更新滑块图形

this->userHasSetNewValue(newVal);

24 };

};

26

RotaryKnob : InputControl {

28 public:

相应地

旋转旋钮图像 30 void updateValue( int newValue );

void acceptUserClick() {

32 确定新值,设置它

int newVal;

34 更新旋钮图片

this->userHasSetNewValue(newVal);

36 };

};

38

class GraphicsContext {

40 public:

virtual void render(int);

42 };



44 class DisplayWidget : public UIWidget {

protected:

46 每个子类都应该将 GraphicsContext

设置为

对其需求
有意义的 48
GraphicsContext* gc;

50 public:

void updateValue( int newValue ) {

52 gc->render( newValue );

};

54 };

56 TextWidget : public DisplayWidget {};



58 BarGraph : public DisplayWidget {};

   class UIWidget {

 2 public:

       virtual void updateValue( int newValue );

 4 };



 6 class InputControl : UIWidget{

       UIWidget*   target;

 8 public:

       void userHasSetNewValue(int myNewValue) {

10         target->updateValue(myNewValue);

           // ^--- Trusted Delegation

12     }

   };

14

   class SliderBar : public InputControl {

16 public:

       // Moves the slider bar accordingly

18     void updateValue( int newValue );

       void acceptUserClick() {

20         // Determine new value, set it

           int newVal;

22         // Update the slider bar graphic

           this->userHasSetNewValue(newVal);

24     };

   };

26

   class RotaryKnob : InputControl {

28 public:

       // Rotates the knob image accordingly

30     void updateValue( int newValue );

       void acceptUserClick() {

32         // Determine new value, set it

           int newVal;

34         // Update the knob image

           this->userHasSetNewValue(newVal);

36     };

   };

38

   class GraphicsContext {

40 public:

       virtual void render(int);

42 };



44 class DisplayWidget : public UIWidget {

   protected:

46     // Each subclass should set the

       // GraphicsContext to something

48     // meaningful for its needs

       GraphicsContext*    gc;

50 public:

       void updateValue( int newValue ) {

52         gc->render( newValue );

       };

54 };

56 class TextWidget : public DisplayWidget {};



58 class BarGraph : public DisplayWidget {};


在此示例中,和 类在 Trusted Delegation 模式中扮演必要的角色,如图 5.3 所示。 对 call to 将做什么有更大的确定性,因为它知道并通过子类化与 class 紧密相连。如果对象是某种其他类型的,那么关于该调用将完成什么的信息将要少得多。知道它是其子类之一的实例或子类的实例表明 delegate 对象将像它一样与用户相关。它可以相信其委派的任务将按预期处理。InputControlUIWidgetInputControlupdateValueUIWidgettargetInputControlupdateValue()UIWidgetInputControl

In this example, the InputControl and UIWidget classes fulfill the necessary roles in the Trusted Delegation pattern, as shown in Figure 5.3. InputControl has a greater sense of certainty about what the call to updateValue will do, because it knows about and is intimately tied with the UIWidget class through subclassing. If the target object were of some other type, then InputControl would have much less information about what that call to updateValue() would accomplish. Knowing that it is an instance of UIWidget or of one of its subclasses tells InputControl that the delegate object will be involved with the user, just as it is. It can trust that its delegated task will be handled as expected.

图像

图 5.3.显示 Trusted Delegation 实例的 UI 类群集。

Figure 5.3. UI class cluster showing an instance of Trusted Delegation.

and 对象不必知道它们的值去向,事实上,它们可以相互绑定,每个对象都同步调整对方。您甚至可以将同一子类的两个对象绑定在一起,例如两个实例。我们已经将谁发送什么数据和谁在接收数据等问题分开。我们唯一关心的是数据以多态方式发送到正确接收的客户端,并且当前的调用对象属于该多态系列。这允许为许多类提供单个统一的接口,这些类可以协同工作以执行许多任务。SliderBarRotaryKnobInputControlSliderBar

The SliderBar and RotaryKnob objects do not have to know anything about where their value is going and, in fact, they could be tied to each other, with each adjusting the other in sync. You could even have two objects of the same InputControl subclass tied together, such as two SliderBar instances. We have separated the concerns of who is sending what data and who is receiving it. Our only concerns are that the data is being sent to a properly receiving client polymorphically and that the current calling object is of that polymorphic family. This allows for a single, unified interface for many classes that can work in tandem to perform many tasks.

适用性

在以下情况下使用 Trusted Delegation

Use Trusted Delegation when:

• 委派是适当的,需要执行相关和/或不相关的子任务。

• Delegation is appropriate, with related and/or unrelated subtasks to be performed.

• 在任务的要求和/或实施中需要一定程度的假定信任。

• A level of assumed trust is required in the requirements and/or implementation for the task.

• 实际实现可能无法提前选择,并且可能需要多态性才能正确处理消息请求。

• The actual implementation may not be selectable ahead of time, and polymorphism may be required to properly handle the message request.

• 调用对象属于多态类层次结构中的类型。

• The calling object is of a type in the polymorphic class hierarchy.

结构
图像
参与者

FamilyHead (全家掌门)

FamilyHead

多态类 cluster 的基类。

The base class for a polymorphic class cluster.

委托人

Delegator

的子类 .FamilyHead

A subclass of FamilyHead.

目标

target

该 的多态实例包含在 中。FamilyHeadDelegator

A polymorphic instance of FamilyHead that is contained by Delegator.

操作

operation

请求受信任的委托任务的调用方法。

The calling method that is requesting a trusted delegated task.

操作 2

operation2

被调用的 trust 方法将执行委托的任务。

The called method trusted to perform the delegated task.

合作

FamilyHead为 提供了一个基本接口,并且 的实例也驻留在 to handle requests 中。此实例被理解为多态实例,并且可能以多种不同的方式处理请求。“受信任的委派”与“受信任的重定向”的不同之处在于,后者是一种更通用的形式,并打包出与启动方法相关(而不是优化)的子任务。DelegatorFamilyHeadDelegator

FamilyHead provides a base interface for Delegator, and an instance of FamilyHead also resides within the Delegator to handle requests. This instance is understood to be polymorphic and may handle a request in a number of different ways. Trusted Delegation differs from Trusted Redirection in that it is a more generalized form and parcels out subtasks that are related to, not refinements of, the initiating method.

后果

与任何子类型关系一样, 与 的接口相关联。该方法的实现受其最终包含的特定子类的约束,通过多态性。因此,如果类层次结构中的另一个类以意外的方式实现其方法,则 Trusted Delegation 可能会设置意外后果。另一方面,这是一种强大的机制,可通过向类族添加类来扩展功能。如果需要以某种方式限制可能的扩展,请考虑改用 Deputized Delegation 模式。DelegatorFamilyHeadtarget

As with any subtyping relationship, Delegator is tied to the interface of FamilyHead. The implementation of the target method is subject to the particular subclass that it ends up being contained in, via polymorphism. For this reason, Trusted Delegation may set up unintended consequences if another class within the class hierarchy implements its methods in an unexpected way. On the other hand, this is a powerful mechanism for extending functionality by adding classes to the class family. If the possible extensions need to be limited in some way, consider using the Deputized Delegation pattern instead.

实现

可以在 Base Class 或 Subclass 中定义目标;它只需要可以从 subclass 中访问。

The target can be defined in either the base class or the subclass; it just needs to be accessible from with the subclass.

在 Java 中,超类定义的 target:

In Java, superclass-defined target:


   public 抽象类FamilyHead {

2 FamilyHead target;

public abstract void operation();

4 public abstract void operation2();

};



6 public class Delegator extends FamilyHead {

8 public void operation() {

可选工作之前...

10 target.operation2();

^--- 受信任的委派

12 可选工作之后...

};

14 public void operation2() {};

   };

   public abstract class FamilyHead {

 2     FamilyHead  target;

       public abstract void operation();

 4     public abstract void operation2();

   };

 6

   public class Delegator extends FamilyHead {

 8     public void operation() {

           // Optional work before...

10         target.operation2();

           // ^--- Trusted Delegation

12         // Optional work after...

       };

14     public void operation2() {};

   };


在 Python 中,子类定义的 target:

In Python, subclass-defined target:


 1 FamilyHead:

def operation(self):

3

def operation2(self):

5

};



7 Delegator(FamilyHead):

9 目标 = FamilyHead();

def operation(self) {

11 # 可选工作之前...

target.operation2() 操作

13 # ^--- 受信任的委托

# 可选工作之后...

 1 class FamilyHead:

       def operation(self):

 3         pass

       def operation2(self):

 5         pass

   };

 7

   class Delegator(FamilyHead):

 9     target = FamilyHead();

       def operation(self) {

11         # Optional work before...

           target.operation2();

13         # ^--- Trusted Delegation

           # Optional work after...


相关模式

正如您可能从名称中猜到的那样,Trusted DelegationDelegation 的专门版本,但委托将转到受信任类型的对象,因为该类型的委托对象是调用对象的超类型。删除此类型关系会导致此模式变回普通的 Delegation。另一方面,对这种类型关系施加更严格的限制并将其转换为同级类型关系会产生 Deputized Delegation。对类型施加更严格的限制,使其与调用对象完全相同,从而创建 Delegated Conglomeration 的实例。保持类型简洁性相同,但修改方法相似性,以便我们在委托对象中调用类似的方法,从而获得可信重定向。最后,将对象折叠为同一个对象,以便我们在同一个对象上调用方法的超类型实现,我们得到 Revert Method

As you might guess from the name, Trusted Delegation is a specialized version of Delegation, but the delegation is going to an object of a trusted type, by virtue of that type of the delegate object being a supertype of the calling object. Removing this typing relation results in turning this pattern back into a plain Delegation. On the other hand, putting even tighter restrictions on this typing relationship and turning it into a sibling type relationship yields Deputized Delegation. Putting yet tighter restrictions on the type, making it exactly the same as the calling object, creates an instance of Delegated Conglomeration. Keeping the type smiliarity the same but modifying the method similarity such that we’re calling the similar method in the delegate object gets us to Trusted Redirection. Finally, collapsing the objects into the same one, such that we’re calling the supertype implementation of a method on the same object, we arrive at Revert Method.

方法调用分类

对象:类似

Object: Similar

对象类型:

Object Type: Subtype

方法:不同

Method: Dissimilar

图像

受信任的重定向

Trusted Redirection

对象行为

Object Behavioral

意图

将方法实现的某些部分重定向到当前类所属的可能类群集。这些类通过类型关系被认为是可信的。

Redirect some portion of a method’s implementation to a possible cluster of classes of which the current class is a member. The classes are considered trustworthy through the typing relationships.

赋予动机

通常,相关目标文件的分层目标文件结构是在运行时构建的,并且需要在使用不同实现的多个目标文件之间分配行为。这是通过多个对象转移处理请求的责任的有效方法,它是责任链复合模式的核心结构 [21]。可信重定向可以被视为此类链的单个链接。

Frequently, a hierarchical object structure of related objects is built at runtime, and behavior needs to be distributed among multiple objects operating with differing implementations. This is an effective way to move responsibility for the handling of a request through a number of objects, and it is a core structure in the Chain of Responsibility and Composite patterns [21]. Trusted Redirection can be considered a single link of such chains.

尽管在这些情况下我们可以简单地使用 Redirection,但相关行为链比简单的方法相似性假设更严格的要求。意图必须相同,而不仅仅是相似。通常,甚至 implementations 也被假定具有一些通用性。

Although we could simply use Redirection in these cases, chains of related behavior assume more stringent requirements than simple method similarity. The intents must be identical, not just similar. Frequently, even the implementations are assumed to have some commonality.

受信任的委派一样,在调用对象和被调用对象之间建立信任级别,其基础是被调用对象属于非常密切相关的类型。但是,使用 Trusted Redirection 时,这种信任会更强一些。

Just as in Trusted Delegation, a level of trust is built between the calling object and the called object, based on the knowledge that the called object is of a very closely related type. With Trusted Redirection, however, that trust is just a little bit stronger.

使用 Redirection 意味着调用方法和目标方法背后的意图相似,并且这两种方法之间存在很强的相关性。通过确保接收对象的类型是当前对象的超类型,可以建立更强的相关性。声明目标对象的类型必须是当前对象的类型的超类或派生自当前对象的类型的超类,从而设置一个可信类型池。这些类型应具有在形式和意图上更接近触发调用的方法的行为。

The use of a Redirection implies that the intent behind the calling method and the target method are similar and that a strong correlation exists between the two methods. By ensuring that the type of the recipient object is a supertype of the current object, a much stronger correlation can be established. Declaring that the target object must be of a type that is, or is derived from, a superclass of the current object’s type, sets up a pool of trusted types. These types are expected to have behavior that is closer in form and intent to the method triggering the call.

例如,在事件处理系统(如 GUI 中)中,通常具有事件感知元素的层次结构。数据视图和控件包含在窗格中,这些窗格由多个窗口组成,这些窗口位于全局 UI 环境中,可能还有其他元素。可以要求这些元素中的任何一个来处理用户生成的事件(如鼠标单击),但它可能是上下文驱动的处理。例如,位于后台且当前处于非活动状态的窗口对鼠标事件的反应可能与前台中的窗口不同。如果窗口处于非活动状态,它将出现在前台;但是,如果窗口处于活动状态,它会将 Click 事件重定向到其内容,以便他们可以处理它。这些内容可以是选项卡式子窗格、控件小部件、文本框,或者任何东西。选项卡式子窗格可能同样处于活动状态或非活动状态,并决定如何对单击做出反应。反过来,文本框可以根据其状态选择将单击传递给文本内容(如果合适)。文本内容可以在内部对单击、拖动或其他事件做出反应,同时让文本框处理视觉方面。并非所有事件处理程序都有可视组件。所有视觉元素都应该能够适当地处理事件,但可能不会要求它们在任何特定时间点都以相同的方式处理它们。这种时间动态性是导致使用 Trusted Redirection 的常见但并非必要的约束。

For example, in event-handling systems, such as found in GUIs, it is common to have a hierarchy of event-aware elements. Data views and controls are contained within panes, which are composed into windows, which sit in a global UI environment, perhaps with other elements. Any of these elements may be asked to handle a user-generated event such as a mouse-click, but it may be a context-driven handling. A window that is in the background and currently inactive may react differently to mouse events than one in the foreground, for example. If the window is inactive, it will come to the foreground; but if the window is active, it will redirect the click event to its contents, so they can handle it. Those contents could be a tabbed subpane, a control widget, a text box, anything at all. A tabbed subpane may be similarly active or inactive and decide how to react to the click. A text box may, in turn, elect to pass along the click to the text contents if appropriate, based on its state. The text contents can react to the click, drag, or other event internally while letting the text box handle the visual aspects. Not all event handlers have a visual component. All the visual elements are expected be able to handle an event appropriately, but they may not all be asked to handle them the same way at any specific point in time. This temporal dynamicism is a common but not necessary constraint leading to the use of Trusted Redirection.

事件处理程序实现的示例代码可以用 C++ 定义,如清单 5.30图 5.4 所示。

Example code for an implementation of an event handler could be defined in C++ as in Listing 5.30 and in Figure 5.4.

图像

图 5.4.显示 Trusted Redirection 实例的 UI 类群集。

Figure 5.4. UI class cluster showing an instance of Trusted Redirection.

清单 5.30.C++ 中的事件处理程序显示受信任的重定向

Listing 5.30. Event handler in C++ showing Trusted Redirection.


   #include <vector>

2 使用 std::vector;



4 Event {};

class 位置 {};



6 MouseEvent : public Event {

8 vector<bool> modifierKeys;

位置位置;

10 bool mouseDown;

};

12

class EventHandler {

14 public:

virtual void handle(Event* e) {

16 do what is needed

};

18 };



20 class TextData : public EventHandler {

Text storage class

22 };

24 UIWidget : public EventHandler {

protected:

26 EventHandler* nextHandler;

public:

28 virtual bool isActive();

virtual void handle(Event* e) {

30 可能的设置...

如果 ( !(this->isActive()) ) {

32 nextHandler->handle(e);

^--- 可信重定向

34 } else {

自己

处理 36 }

};

38 };



40 Button : public UIWidget {

任何可点击按钮

的基类 42 };



44 TabPane : public UIWidget {

UIWidget* contents;

46 public:

TabPane() {

48 设置内容

nextHandler = contents;

50 };

};

52

class TextField : public UIWidget {

54 TextData* text;

public:

56 TextField() {

设置文本

58 nextHandler = text;

};

60 };

   #include <vector>

 2 using std::vector;



 4 class Event {};

   class Position {};

 6

   class MouseEvent : public Event {

 8     vector<bool>    modifierKeys;

       Position        position;

10     bool            mouseDown;

   };

12

   class EventHandler {

14 public:

       virtual void handle(Event* e) {

16         // do what is needed

       };

18 };



20 class TextData : public EventHandler {

       // Text storage class

22 };

24 class UIWidget : public EventHandler {

   protected:

26     EventHandler*   nextHandler;

   public:

28     virtual bool isActive();

       virtual void handle(Event* e) {

30         // Possible setup...

           if ( !(this->isActive()) ) {

32             nextHandler->handle(e);

               // ^--- Trusted Redirection

34         } else {

               // Handle it myself

36         }

       };

38 };



40 class Button : public UIWidget {

       // Base class for any clickable button

42 };



44 class TabPane : public UIWidget {

       UIWidget*   contents;

46 public:

       TabPane() {

48         // Setup contents

           nextHandler = contents;

50     };

   };

52

   class TextField : public UIWidget {

54     TextData*   text;

   public:

56     TextField() {

           // Setup text

58         nextHandler = text;

       };

60 };


每个可视 UI 子类都继承了 的默认行为,尽管它可以随时被覆盖。在此结构中,每个子类都使用一个实例作为下一个项目,以便在当前对象不合适的情况下处理事件。对此行为的常见更改包括在决定是否处理事件之前检查已传入的事件类型。UIWidgetEventHandler

Each visual UI subclass inherits the default behavior from UIWidget, although it can be overridden at any time. In this structure, each subclass uses an instance of EventHandler as the next item to be asked to handle an event in the case that the current object is not suitable. A common alteration to this behavior includes checking to see what type of event has been passed in before making a decision regarding whether to handle it.

适用性

在以下情况下使用受信任的重定向

Use Trusted Redirection when:

• 相关对象的聚合结构应在编译或运行时组合。

• An aggregate structure of related objects is expected to be composed at compile or runtime.

• 应将行为分解为各种成员对象。

• Behavior should be decomposed to the various member objects.

• 无法提前知道聚合对象的结构。

• The structure of the aggregate objects is not known ahead of time.

• 多态行为是预期的,但不是强制性的。

• Polymorphic behavior is expected but not enforced.

结构
图像
参与者

FamilyHead (全家掌门)

FamilyHead

定义接口并包含可能被覆盖的方法。

Defines interface and contains a method to be possibly overridden.

重定向器

Redirector

使用 的接口并将内部行为重定向回 的实例,以获得无定形对象结构上的多态行为。FamilyHeadFamilyHead

Uses interface of FamilyHead and redirects internal behavior back to an instance of FamilyHead to gain polymorphic behavior over an amorphous object structure.

目标

target

请求执行重定向工作的对象。

The object that is being requested to perform the redirected work.

操作

operation

指示要在调用对象和被调用对象中完成的预期任务。

Indicates the intended task to be completed in both the calling and called objects.

合作

Redirector依赖于接口的类和对象递归实现的相同实例。重定向关系是此模式的关键部分,因为它推动了“执行与我被要求执行的操作相同的操作”的概念。可信方面来自将工作重定向到当前对象的超类型以进行多态处理。FamilyHead

Redirector relies on the class FamilyHead for an interface and an instance of the same for an object-recursive implementation. The Redirection relationship is a critical part of this pattern because it drives the concept of “do the same as I was asked to do.” The trusted aspect comes in from redirecting the work to a supertype of the current object for polymorphic handling.

后果

Redirector依赖于其接口,但它属于整个 Class 层次结构来提供各种实现。因此,层次结构中的其他类可能会表现出意外的行为。如果发生意外行为,请考虑改用 Deputized Redirection 模式将可能性限制为可管理的集合。FamilyHead

Redirector is reliant on FamilyHead for its interface, but it falls to the entirety of the class hierarchy to provide the various implementations. For this reason, other classes in the hierarchy may exhibit unexpected behavior. If unexpected behavior is occurring, consider instead using the Deputized Redirection pattern to restrict the possibilities to a manageable set.

实现

在 C++ 中:

In C++:


   FamilyHead {

2 public:

virtual void operation();

4 };



6 重定向器:public FamilyHead {

public:

8 void operation();

FamilyHead* 目标;

10 };



12 void

Redirector::operation() {

14 之前可选工作...

target->operation();

16 可选工作后...

   }

   class FamilyHead {

 2 public:

       virtual void operation();

 4 };



 6 class Redirector : public FamilyHead {

   public:

 8     void operation();

       FamilyHead* target;

10 };



12 void

   Redirector::operation() {

14     // Optional work before...

       target->operation();

16     // Optional work after...

   }


相关模式

可信重定向Redirection 的一种专用化,因此重定向任务的可能目标来自定义为目标类型的相互子类的可信类集群。因此,放松这种信任可以让您回到更简单、更通用的 EDP。进一步将该信任限制为超类的同级子类的子集会导致 Deputized Redirection。如果需要完全相同类型的目标对象且没有可变性,请查看 重定向递归 了解如何使用设置为相同类型的对象类型相似性。保留子类型关系,但将调用对象和目标对象合并为一个对象,从而产生 Extend Method。只需打破方法相似性,我们就会进入 Trusted Delegation

Trusted Redirection is a specialization of Redirection such that the possible targets of the redirected task are from a trusted cluster of classes defined as mutual subclasses of a the target type. As such, relaxing that trust lets you go back to that simpler, more general EDP. Further restricting that trust to a subset of the sibling subclasses of the superclass results in Deputized Redirection. If the exact same type of target object is required with no variability, then look to Redirected Recursion for how to work with object type similarity set to the same type. Retaining the subtyping relationship but coalescing the calling and target objects into one object gives rise to Extend Method. Simply breaking the method similarity moves us to Trusted Delegation.

方法调用分类

对象:类似

Object: Similar

对象类型:

Object Type: Subtype

方法:类似

Method: Similar

图像

代理代表团

Deputized Delegation

对象行为

Object Behavioral

意图

当我们需要 Trusted Delegation 的 trusted delegation 行为,但发现它过于笼统时,有必要为受限多态性预先选择类层次结构的子树。

When we need the trusted delegation behavior of Trusted Delegation but find it too generalized, it is necessary to preselect a subtree of the class hierarchy for a restricted polymorphism.

赋予动机

静态类型是一种从定义完善的池中预先选择类型并在运行时调用系统之前形成对象类型的更具体概念的方法。这种预选是添加类型安全的一种方式 — 我们确切地知道对象在执行过程中使用时将具有哪些功能。另一方面,多态性是一种在运行时之前抽象出类型信息的技术。我们特意混淆了对象底层类型的许多细节,以牺牲类型安全为代价提供灵活性。有时我们需要在两者之间取得平衡。

Static typing is a way to preselect types from a well-defined pool and form more concrete notions of an object’s type before the invocation of a system at runtime. This preselection is a way of adding type safety—we know precisely what capabilities an object will have when used during execution. Polymorphism, on the other hand, is a technique for abstracting out typing information until runtime. We purposefully obfuscate many details of the underlying type of the object, providing flexibility at the cost of type safety. Sometimes we need a balance of the two.

请考虑 Trusted Delegation 中的驱动示例。缺少一个常见的数据输入控件,一个用于输入文本的控件。按照设置方式,子类化的类将能够更改显示数值的值,这可能不是设计人员想要的。7 Trusted Delegation 的设计可以通过添加 Listing 5.31 中所示的类和更改来微调。TextInputInputControlBarGraph

Consider the driving example from Trusted Delegation. A common data-input control is missing, one to enter text. The way things are set up, a TextInput class that subclassed from InputControl would be able to alter the value of a BarGraph showing numeric values, and this may not be what the designer wants.7 The design from Trusted Delegation can be fine-tuned by adding the classes and changes shown in Listing 5.31.

清单 5.31.演示 C++ 中的代理委派的 UI 小部件。

Listing 5.31. UI widgets demonstrating Deputized Delegation in C++.


 1 class UIWidget {

public:

3 virtual void updateValue( int newValue );

};



5 InputControl : UIWidget{

7 UIWidget* target;

public:

9 void userHasSetNewValue(int myNewValue) {

target->updateValue(myNewValue);

11 }

};

13

SliderBar : InputControl {

15 public:

相应地

移动滑块条 17 void updateValue( int newValue );

void acceptUserClick() {

19 确定新值,设置它

int newVal;

21 更新滑块图形

this->userHasSetNewValue(newVal);

23 };

};

25

RotaryKnob : InputControl {

27 public:

相应地

旋转旋钮图像 29 void updateValue( int newValue );

void acceptUserClick() {

31 确定新值,设置

int newVal;

33 更新旋钮图片

this->userHasSetNewValue(newVal);

35 };

};

37

class GraphicsContext {

39 public:

virtual void render(int);

41 };



43 DisplayWidget : public UIWidget {

protected:

45 每个子类都应该将 GraphicsContext 设置为



对其需求
有意义的 47 值
GraphicsContext* gc;

49 public:

void updateValue( int newValue ) {

51 gc->render( newValue );

};

53 };



55 TextWidget : public DisplayWidget {};



57 BarGraph : public DisplayWidget {};



59 支持代理委派

的新类 TextInputControl : public InputControl {

61 TextWidget* textTarget;

public:

63 void userHasSetNewValue(int myNewValue) {

textTarget->updateValue(myNewValue);

65 ^--- 代理代表团

};

67 };

 1 class UIWidget {

   public:

 3     virtual void updateValue( int newValue );

   };

 5

   class InputControl : UIWidget{

 7     UIWidget*   target;

   public:

 9     void userHasSetNewValue(int myNewValue) {

           target->updateValue(myNewValue);

11     }

   };

13

   class SliderBar : InputControl {

15 public:

       // Moves the slider bar accordingly

17     void updateValue( int newValue );

       void acceptUserClick() {

19         // Determine new value, set it

           int newVal;

21         // Update the slider bar graphic

           this->userHasSetNewValue(newVal);

23     };

   };

25

   class RotaryKnob : InputControl {

27 public:

       // Rotates the knob image accordingly

29     void updateValue( int newValue );

       void acceptUserClick() {

31         // Determine new value, set it

           int newVal;

33         // Update the knob image

           this->userHasSetNewValue(newVal);

35     };

   };

37

   class GraphicsContext {

39 public:

       virtual void render(int);

41 };



43 class DisplayWidget : public UIWidget {

   protected:

45     // Each subclass should set the

       // GraphicsContext to something

47     // meaningful for its needs

       GraphicsContext*    gc;

49 public:

       void updateValue( int newValue ) {

51         gc->render( newValue );

       };

53 };



55 class TextWidget : public DisplayWidget {};



57 class BarGraph : public DisplayWidget {};



59 // New class to support Deputized Delegation

   class TextInputControl : public InputControl {

61     TextWidget* textTarget;

   public:

63     void userHasSetNewValue(int myNewValue) {

           textTarget->updateValue(myNewValue);

65         // ^--- Deputized Delegation

       };

67 };


现在 可用于更改类的实例,但它将无法更改其他非文本显示项的实例。 并且同样可以限制为基于 的类层次结构,例如。这在图 5.5 中可能更容易看到。TextInputControlTextFieldBar-GraphSliderBarRotaryKnobNumericWidget

Now the TextInputControl is available for altering instances of the TextField class, but it will not be able to alter instances of Bar-Graph or other nontext display items. SliderBar and RotaryKnob could similarly be limited to a class hierarchy based on a NumericWidget, for example. This may be easier to see in Figure 5.5.

图像

图 5.5.显示 Deputized Delegation 实例的 UI 类群集。

Figure 5.5. UI class cluster showing an instance of Deputized Delegation.

适用性

在以下情况下使用 Deputized Delegation

Use Deputized Delegation when:

“受信任的委派”是适当的常规模式。

Trusted Delegation is the appropriate general pattern.

• 需要更好地控制可能的对象类型。

• Greater control over the possible types of objects is required.

• 要委派给的类型不包括调用对象的类型。

• The types to be delegated to do not include the calling object’s type.

参与者

FamilyHead (全家掌门)

FamilyHead

多态类群集的基类,用于建立基本的通用接口和语义。

The base class for a polymorphic class cluster, which establishes the basic generalized interface and semantics.

委托人

Delegator

它的子类需要将 subtask 委托给受信任的委托。FamilyHead

A subclass of FamilyHead that requires a subtask to be delegated to a trusted delegate.

DelegateSibling

DelegateSibling

它的另一个 subclass 具有 subtask 的适当语义。FamilyHead

Another subclass of FamilyHead that has the appropriate semantics for the subtask.

目标

target

该实例的 包含在 和 将被要求执行 subtask 中。DelegateSiblingDelegator

An instance of DelegateSibling that is contained by Delegator and will be asked to perform the subtask.

操作

operation

发送要执行的 subtask 的请求的调用方法。

The calling method sending the request for the subtask to be performed.

操作 2

operation2

被要求执行 subtask 的被调用方法。

The called method being asked to perform the subtask.

结构
图像
合作

除了 Trusted Delegation 中涉及的协作之外,此模式还将类添加为实现的目标。通过这样做,它将可能的行为限制为公共 ancestor 类的子类,从而选择优化的语义。DelegateSiblingoperation

In addition to the collaborations involved in Trusted Delegation, this pattern adds the DelegateSibling class as the target for the operation implementation. By doing so, it limits the possible behavior to a subclass of the common ancestor class and therefore to select refined semantics.

后果

此模式与其表亲 Trusted Delegation 的不同之处在于,它的设计决策是将多态性静态限制为原始类系列的子树。它可以在包含复杂性方面提供很大的好处,但也可能在以后导致问题,例如,如果确定所选的同级类限制太多。尽管对代码的语法更改很小,但添加更广泛的可能类和行为集可能会产生深远的影响,应仔细考虑。还要注意,与 Trusted Delegation 不同,它不能是共享超类的成员,因为超类必须具有其子类之一的先验知识。尽管某些语言可以完成这种特殊的技巧,但这不是一个可靠的设计决策,应尽可能避免。target

This pattern differs from its cousin, Trusted Delegation, in that a design decision is made to limit the polymorphism statically to a subtree of the original class family. It can provide large benefits in containing complexity, but it can also lead to issues later if, for example, the chosen sibling class is determined to be too limiting. Although the syntactic change to the code is minimal, the addition of a broader set of possible classes and behaviors can have far-reaching effects that should be carefully considered. Also note that unlike Trusted Delegation, the target cannot be a member of the shared superclass, because the superclass would have to have prior knowledge of one of its subclasses. Although some languages can pull off this particular bit of trickery, it’s not a robust design decision and should be avoided whenever possible.

实现

在 C++ 中:

In C++:


 1 FamilyHead {

protected:

3 void operation();

无效操作 2();

5 };



7 DelegateSibling : public FamilyHead {

void operation2();

9 };



11 Delegator : public FamilyHead {

DelegateSibling* target;

13 void operation() {

可选工作 15

target->operation2();

^--- 代表代表团

17 之后的可选工作

};

19 };

 1 class FamilyHead {

   protected:

 3     void operation();

       void operation2();

 5 };



 7 class DelegateSibling : public FamilyHead {

       void operation2();

 9 };



11 class Delegator : public FamilyHead {

       DelegateSibling*    target;

13     void operation() {

           // Optional work prior

15         target->operation2();

           // ^--- Deputized Delegation

17         // Optional work after

       };

19 };


相关模式

Deputized Delegation 相当专业,只能导致几个更通用的 EDP。如果使用可信类集群的标准略微放宽,以便将对象类型相似性设置为子类型,我们将返回可信委派。如果正在调用的方法更改为与调用者类似的方法,则我们滑到 Deputized Redirection。在这种情况下,将调用对象和委托对象折叠为一个没有多大意义,我们最终会得到格式错误的 EDP 设计空间位置。

Deputized Delegation is rather specialized and can only lead to a couple of more general EDPs. If the criteria of using a trusted class cluster is relaxed slightly such that the object type similarity is set to subtyping, we get back to Trusted Delegation. If the method being invoked is changed to one that is similar to the caller, then we slide over to Deputized Redirection. This is one case where collapsing the calling and delegate objects into one doesn’t make much sense, and we end up with ill-formed EDP design space positions.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:兄弟

Object Type: Sibling

方法:不同

Method: Dissimilar

图像

代理重定向

Deputized Redirection

对象行为

Object Behavioral

意图

当我们需要 Trusted Redirection 的可信重定向行为,但发现它仍然过于笼统时,有必要为受限多态性预先选择类层次结构的子树。此选定的类子树将不包括发起重定向的类。

When we need the trusted redirection behavior of Trusted Redirection but find it still too generalized, it is necessary to preselect a subtree of the class hierarchy for a restricted polymorphism. This selected subtree of classes will not include the class originating the redirection.

赋予动机

静态类型是一种从定义完善的池中预先选择类型并在运行时调用系统之前形成对象类型的更具体概念的方法。这种预选是添加类型安全的一种方式 — 我们确切地知道对象在执行过程中使用时将具有哪些功能。另一方面,多态性是一种在运行时之前抽象出类型信息的技术。我们特意混淆了对象底层类型的许多细节,以牺牲类型安全为代价提供灵活性。有时我们需要在两者之间取得平衡。

Static typing is a way to preselect types from a well-defined pool and form more concrete notions of an object’s type before the invocation of a system at runtime. This preselection is a way of adding type safety—we know precisely what capabilities an object will have when used during execution. Polymorphism, on the other hand, is a technique for abstracting out typing information until runtime. We purposefully obfuscate many details of the underlying type of the object, providing flexibility at the cost of type safety. Sometimes we need a balance of the two.

Trusted Redirection 的驱动示例中,该代码提供了一个非常通用的事件处理系统。但是,这种开放式类型层次结构并不适合所有情况。例如,滑块条仅响应鼠标事件,除非在特殊情况下,否则不会响应击键。同样,文本字段将同时响应鼠标事件和击键,但击键的处理可能最好由基础文本数据对象来处理。因此,可以通过简单的优化在运行时将整个事件类从考虑中排除。从 Trusted Redirection 的 Trusted RedirectionListing 5.30 中的代码开始,我们可以将其更改为类似于 Sourced 5.32 中的代码。

In the driving example for Trusted Redirection, the code provided a very general event-handling system. Such an open-ended type hierarchy, however, is not appropriate for all cases. For instance, a slider bar is only going to respond to mouse events, and not, except in special circumstances, to keystrokes. Likewise, a text field will respond to both mouse events and keystrokes, but the handling of keystrokes may be best taken care of by an underlying text data object. Entire classes of events can therefore be eliminated from consideration at runtime by a simple refinement. Starting with the code in Listing 5.30 from Trusted Redirection, we can alter it to look like the code in Listing 5.32.

清单 5.32.演示 C++ 中的代理重定向的 UI 小组件。

Listing 5.32. UI widgets demonstrating Deputized Redirection in C++.


 1 #include <vector>

使用 std::vector;



3 位置 {};

5 Key {};

class Event {

7 public:

virtual int getID();

9 };



11 MouseEvent : public Event {

vector<bool> modifierKeys;

13 位置;

布尔 mouseDown;

15 };



17 KeyEvent : public Event {

vector<bool> modifierKeys;

19 钥匙;

bool keyDown;

21 };



23 class EventHandler {

public:

25 virtual void handle(Event* e) {

做需要

做的事 27 };

};

29

class TextData : public EventHandler {

31 public:

Text storage class

33 virtual void handle(KeyEvent* e) {

做需要

做的事 35 };

};

37

class UIWidget : public EventHandler {

39 protected:

EventHandler* nextHandler;

41 public:

bool isActive();

43 virtual void handle(Event* e) {

可能的设置...

45 if ( !(this->isActive()) ) {

nextHandler->handle(e);

47 } else {

自己

处理 49 }

};

51 };



53 Button : public UIWidget {

任何可点击按钮

的基类 55 };



57 TabPane:public UIWidget {

UIWidget* 内容;

59 public:

TabPane() {

61 设置内容

nextHandler = contents;

63 };

};

65

class TextField : public UIWidget {

67 TextData* text;

public:

69 TextField() {

设置文本

71 nextHandler = text;

};

73 virtual void handle(Event* e) {

e 是 KeyEvent 还是子类?

75 ifdynamic_cast<KeyEvent*>(e)) {

77 之前

可选工作 text->handle(dynamic_cast<KeyEvent*>(e));

^--- 代理重定向

79 可选工作

} else {

81 Handle MouseEvents

}

83     };

   };

 1 #include <vector>

   using std::vector;

 3

   class Position {};

 5 class Key {};

   class Event {

 7 public:

       virtual int getID();

 9 };



11 class MouseEvent : public Event {

       vector<bool>    modifierKeys;

13     Position        position;

       bool            mouseDown;

15 };



17 class KeyEvent : public Event {

       vector<bool>    modifierKeys;

19     Key         key;

       bool            keyDown;

21 };



23 class EventHandler {

   public:

25     virtual void handle(Event* e) {

           // do what is needed

27     };

   };

29

   class TextData : public EventHandler {

31 public:

       // Text storage class

33     virtual void handle(KeyEvent* e) {

           // do what is needed

35     };

   };

37

   class UIWidget : public EventHandler {

39 protected:

       EventHandler*   nextHandler;

41 public:

       bool isActive();

43     virtual void handle(Event* e) {

           // Possible setup...

45         if ( !(this->isActive()) ) {

               nextHandler->handle(e);

47         } else {

               // Handle it myself

49         }

       };

51 };



53 class Button : public UIWidget {

       // Base class for any clickable button

55 };



57 class TabPane : public UIWidget {

       UIWidget*   contents;

59 public:

       TabPane() {

61         // Setup contents

           nextHandler = contents;

63     };

   };

65

   class TextField : public UIWidget {

67     TextData*   text;

   public:

69     TextField() {

           // Setup text

71         nextHandler = text;

       };

73     virtual void handle(Event* e) {

           // Is e a KeyEvent or subclass?

75         if (dynamic_cast<KeyEvent*>(e)) {

               // Optional work before

77             text->handle(dynamic_cast<KeyEvent*>(e));

               // ^--- Deputized Redirection

79             // Optional work after

           } else {

81             // Handle MouseEvents

           }

83     };

   };


新结构如图 5.6 所示。我们添加了类,现在覆盖了 .它自己处理 的实例,但以前它只是简单地将 设置为 instance .a 有可能通过 bug 溜走并绊倒 的实现 ,但这样,唯一交给的事件是 的事件。 并且具有比 do 和 的其他子类更高的信任级别。KeyEventTextFieldhandle()MouseEventnext-HandlerTextDatatextMouseEventTextDatahandle()TextDataKeyEventTextFieldTextDataTextFieldEvent-Handler

The new structure is shown in Figure 5.6. We added the KeyEvent class, and TextField now overrides handle(). It handles instances of MouseEvent on its own, but previously it simply set the next-Handler to the TextData instance text. There was the chance that a MouseEvent could slip through via a bug and trip up TextData’s implementation of handle(), but this way the only events handed to TextData are those of KeyEvent. TextField and TextData have a higher level of trust than do TextField and other subclasses of Event-Handler.

图像

图 5.6.显示 Deputized Redirection 实例的 UI 类群集。

Figure 5.6. UI class cluster showing an instance of Deputized Redirection.

现在,确保它处理 而底层仅处理原始文本交互。 被信任来处理事件,因为更确切地知道预期将如何处理它们。此外,明确地不包括自身作为可能的收件人的类型,就像 Trusted Redirection 中的情况一样。 坚持让与它相关的其他人来照顾这部分工作。TextFieldMouseEventsTextDataTextDataKeyEventTextFieldTextDataTextFieldTextField

Now, TextField ensures that it handles the MouseEvents while the underlying TextData only handles raw text interactions. TextData is trusted to handle the KeyEvent events, because TextField knows more precisely what to expect TextData will do with them. In addition, TextField is specifically not including itself as the type of the possible recipient, as is the case in Trusted Redirection. TextField is insisting that someone else it is related to take care of that portion of the job.

适用性

在以下情况下使用代理重定向

Use Deputized Redirection when:

“受信任的重定向”是适当的常规模式。

Trusted Redirection is the appropriate general pattern.

• 需要更好地控制可能的对象类型。

• Greater control over the possible types of objects is required.

• 要重定向到的类型不包括调用对象的类型。

• The types to be redirected to do not include the calling object’s type.

参与者

FamilyHead (全家掌门)

FamilyHead

定义接口,包含可能被重写的方法,并且是 和 的基类。RedirectorRedirectSibling

Defines interface, contains a method possibly to be overridden, and is the base class for both Redirector and RedirectSibling.

重定向器

Redirector

使用 的接口 ;将内部行为重定向回 的实例,以便在无定形但范围受限的对象结构上获得多态行为。FamilyHeadRedirectSibling

Uses interface of FamilyHead; redirects internal behavior back to an instance of RedirectSibling to gain polymorphic behavior over an amorphous but limited-in-scope object structure.

重定向同级

RedirectSibling

用于多态行为的新类树的头部。

The head of a new class tree for polymorphic behavior.

目标

target

该 的多态实例包含在 中。RedirectSiblingRedirector

A polymorphic instance of RedirectSibling that is contained by Redirector.

操作

operation

调用站点。

The calling site.

结构
图像
合作

Trusted Redirection 一样,受接口和意图的约束。这为特定类型的实现提供了一个概念起点。在这种情况下, 专门将可以作为请求的多态目标的可能类集减少到 的类或子类。和 具有公共基类 in 的事实意味着每个基类都可以对另一个做出更强的假设。RedirectorFamilyHeadRedirectSiblingRedi-rectorRedirect-SiblingRedirectorRedirectSiblingFamilyHead

As with Trusted Redirection, Redirector and FamilyHead are tied by interface and intent. The RedirectSibling provides a conceptual starting point for a particular type of implementation. In this case, Redi-rector specifically reduces the set of possible classes that can be polymorphic targets of the request to either classes or subclasses of Redirect-Sibling. The fact that Redirector and RedirectSibling have a common base class in FamilyHead means that each may make stronger assumptions about the other.

后果

此模式与其表亲 Trusted Redirection 的不同之处在于,已做出设计决策,将多态性静态限制为原始类系列的子树。它可以在包含复杂性方面提供很大的好处,但也可能在以后导致问题,例如,如果确定所选的同级类限制太多。尽管对代码的语法更改很小,但添加更广泛的可能类和行为集可能会产生深远的影响,应仔细考虑。

This pattern differs from its cousin Trusted Redirection in that a design decision has been made to limit the polymorphism statically to a subtree of the original class family. It can provide large benefits in containing complexity, but it can also lead to issues later if, for example, the chosen sibling class is determined to be too limiting. Although the syntactic change to the code is minimal, the addition of a broader set of possible classes and behaviors can have far-reaching effects that should be carefully considered.

实现

在 C++ 中:

In C++:


   FamilyHead {

2 protected:

virtual void operation();

4 };



6 RedirectSibling : public FamilyHead {

void operation();

8 }



10 重定向器:public FamilyHead {

public:

12 RedirectSibling* target;

void operation() {

14 可选工作

prior target->operation();

16 ^--- 代理重定向

18 之后

的可选工作     };

   };

   class FamilyHead {

 2 protected:

       virtual void operation();

 4 };



 6 class RedirectSibling : public FamilyHead {

       void operation();

 8 }



10 class Redirector : public FamilyHead {

   public:

12     RedirectSibling*    target;

       void operation() {

14         // Optional work prior

           target->operation();

16         // ^--- Deputized Redirection

           // Optional work after

18     };

   };


相关模式

由于 Deputized RedirectionTrusted Redirection 的专用化,因此,如果我们通过将类型相似性从同级关系改回更一般的子类型关系来稍微放松信任,则可以返回到该 EDP。或者,我们可以更改方法相似性以调用不同的方法并得到 Deputized Delegation。最后,应该注意的是,在这种情况下,尝试将对象相似度更改为相似是没有意义的,我们在 EDP 设计空间中到达了一个未定义的坐标。

Because Deputized Redirection is a specialization of Trusted Redirection, we can return to that EDP if we relax the trust a bit by changing the type similarity from a sibling relationship back to the more general subtyping relationship. Or, we can change the method similarity to call a dissimilar method and arrive at Deputized Delegation. Finally, it should be noted that attempting to change the object similarity to be similar makes no sense in this case, and we arrive at one of our undefined coordinates in the EDP design space.

方法调用分类

对象:不同

Object: Dissimilar

对象类型:兄弟

Object Type: Sibling

方法:类似

Method: Similar

图像

6. 中间模式组合

6. Intermediate Pattern Compositions

本章建立了直接从 EDP 组成的模式的第一级。我们在第 4 章中看到了这个过程,在这里我们充实了几个常见、简单但尚未基本化的设计模式。和以前一样,第 3 章中的 Pattern Instance Notation (PIN) 用于说明形成这些 pattern 的元件部分。对于 EDP,PIN 表示法用于显示使用 UML 的内部关系。从这里开始,UML 和 PINbox 都出现在 PIN 图中。还包括一个仅限 PINbox 的版本,以说明纯粹的概念性联系。

This chapter establishes the first level of patterns composed directly from EDPs. We saw this process in Chapter 4, and here we flesh out several common, simple, but not-yet-elemental design patterns. As before, the Pattern Instance Notation (PIN) from Chapter 3 is used to illustrate the component portions that form these patterns. With the EDPs, the PIN notation was used to show the internal relationships using UML. From here on out, both UML and PINboxes appear in the PIN diagrams. A PINbox-only version is also included to illustrate the purely conceptual connections.

这些模式非常常见,开发人员不断使用它们,因为它们都很有效,并且还因为它们还以新颖的方式组合了少量简单概念。中间模式不是 EDP,因为它们是可分解的,可以作为更基本概念的组合进行讨论。然而,在讨论这些模式时,基本概念的交集通常并不明确。由学生或开发人员来获得对模式更深层次语义的理解的灵光一现。这假定 Code Pattern 的读者对基本概念有足够的实践,以便在它们出现时识别它们。

These patterns are extremely common, and developers keep coming back to them because they are both effective and because they also combine a small number of simple concepts in novel ways. The intermediate patterns are not EDPs because they are decomposable and can be discussed as the composition of more fundamental concepts. Frequently, however, the intersection of the underlying concepts is not made explicit in discussions of these patterns. It is left up to the student or developer to have that eureka moment of comprehension into the deeper semantics of the pattern. This assumes that the reader of the pattern has sufficient practice with the underlying concepts to recognize them when they occur.

使用 EDP 作为基础,我们可以使构成每个模式的概念的组成和连接点更加明确。本章将向您展示小概念如何快速构建为更复杂的抽象,并演示如何分析已发布的模式以获得新的见解。研究这些中间模式的内部工作原理将为您提供坚实的基础,以便您了解如何结合使用 EDP 和其他模式以形成更大、更丰富的设计模式。

Using the EDPs as a basis, we can make the composition and connection points of the concepts that form each pattern much more explicit. This chapter shows you how small concepts can quickly build into more complex abstractions and demonstrates how to analyze already published patterns for new insights. Studying the inner workings of these intermediate patterns will give you a strong foundation for understanding how EDPs and other patterns can be used in conjunction to form larger and richer design patterns.

本章中的模式规范不包含方法调用 Classification 部分,因为该部分特定于方法调用 EDPS。

The pattern specifications in this chapter do not contain the method-call Classification section, as that was specific to the method-call EDPS.

Fulfill 方法

Fulfill Method

对象结构

Object Structural

意图

为先前抽象的方法提供实现,从而履行 Abstract Interface 所订立的契约,即后面的子类将提供这样的实现。

To provide an implementation for a previously abstracted method, thereby fulfilling the contract made by Abstract Interface that a later subclass would provide such an implementation.

赋予动机

抽象接口 承诺某些子类将为在旨在成为超类的类中声明的方法接口提供适当的方法实现。Inheritance 允许我们定义该子类,而 Fulfill Method 描述了如何通过组合两个模式实例来履行先前的承诺。

Abstract Interface promises that some subclass will provide a proper method implementation to a method interface declared in a class intended to be a superclass. Inheritance lets us define that subclass, and Fulfill Method describes how to go about fulfilling that prior promise by combining the two pattern instances.

这种方法让我们将超类中方法的抽象与任何数量的可能(或可能不会)为其提供实现的可能的子类完全分开。

This approach lets us cleanly separate the abstraction of the method in the superclass from any number of possible subclasses that may (or may not) provide an implementation for it.

适用性

在以下情况下使用 Fulfill Method

Use Fulfill Method when:

• 使用 Abstract Interface 推迟了方法的实施。

• Implementation of a method has been deferred using Abstract Interface.

抽象接口中指定的类的子类能够按承诺处理方法的功能。

• A subclass of the class specified in Abstract Interface is able to handle the functionality of the method as promised.

参与者

abstractor

Abstractor

声明 的接口的类类型。operation

The class type that declares an interface for operation.

配送商

Fulfiller

定义 的方法体的类类型 ;继承自 。operationAbstractor

The class type that defines a method body for operation; inherits from Abstractor.

操作

operation

在 中声明并在 中定义的方法 。AbstractorFulfiller

The method that was declared in Abstractor and defined in Fulfiller.

结构
图像

仅 PINbox 版本(也用于图 4.4):

PINbox-only version (also used in Figure 4.4):

图像
合作

Abstractor定义方法的接口,并提供实现。 子类 from 来获得适当的类型和接口。FulfillerFulfillerAbstractor

Abstractor defines an interface for a method, and Fulfiller provides the implementation. Fulfiller subclasses from Abstractor to gain the appropriate typing and interface.

AbstractorAbstract Interface 实例中的主类,并且 和 通过 Inheritance 实例关联。AbstractorFulfiller

Abstractor is the primary class in the instance of Abstract Interface, and Abstractor and Fulfiller are related through an instance of Inheritance.

后果

Fulfiller依赖于已实现方法的接口。这两者必须保持同步,有时类层次结构顶部的 shuffle 接口会对其子类产生深远的影响。在预期的情况下,可能首选使用 Delegation 的解决方案。使用 Delegation 的解决方案允许外部接口独立于 implementation 对象。或者,如果只有一个类 / 对象是强制性的,则可以使用 Conglomeration 将任务移交给该类型内部的私有方法,从而保留接口 / 实现封装。Abstractor

Fulfiller relies on Abstractor for the interface to the implemented method. These two must be kept in sync, and there are times when a shuffling interface at the top of a class hierarchy can have far-reaching effects on its subclasses. In cases where this is expected, a solution utilizing Delegation may be preferred. A solution using Delegation allows the external interface to be independent of the implementation object. Alternatively, if only one class/object is mandated, Conglomeration can be used to hand off the task to private methods internal to the type, thereby preserving the interface/implementation encapsulation.

实现

支持继承和抽象方法的每种语言,无论表达式如何,都支持 Fulfill Method。以下是一些基于 Abstract Interface 示例的示例。

Every language that supports inheritance and abstract methods, regardless of expression, supports Fulfill Method. Following are some examples based on the examples from Abstract Interface.

在 C++ 中:

In C++:


 1 Abstractor {

public:

3 virtual void operation() = 0;

};



5 class Fulfiller :

7 public Abstractor {

public:

9 void operation() {

执行适当的工作

11     };

   };

 1 class Abstractor {

   public:

 3     virtual void operation() = 0;

   };

 5

   class Fulfiller :

 7     public Abstractor {

   public:

 9     void operation() {

           // Perform the appropriate work

11     };

   };


在 Java 中,该方法包含在接口中或位于抽象类中:

In Java, the method is included in an interface or is in an abstract class:


   公共接口abstractor {

2 public void operation();

};



4 public abstract class AnotherAbstractor {

6 public abstract void operation2();

public void operation3();

8 };



10 public class Fulfiller

extends AnotherAbstractor

12 implements Abstractor {

public void operation() {

14 执行适当的工作

}

16 public void operation2() {

执行适当的工作

18     }

   };

   public interface Abstractor {

 2     public void operation();

   };

 4

   public abstract class AnotherAbstractor {

 6     public abstract void operation2();

       public void operation3();

 8 };



10 public class Fulfiller

       extends AnotherAbstractor

12     implements Abstractor {

       public void operation() {

14         // Perform the appropriate work

       }

16    public void operation2() {

           // Perform the appropriate work

18     }

   };


在 Python 3.x 中:

In Python 3.x:


1 class Abstractor(metaclass=ABCMeta):

@abstractmethod

3 def operation(self, ...):

// 允许

默认实现 5 return



7 class Fulfiller(Abstractor):

def operation(self, ...):

9 // 执行适当的工作

通行证

1 class Abstractor(metaclass=ABCMeta):

      @abstractmethod

3     def operation(self, ...):

         // Default implementation allowed

5        return



7 class Fulfiller(Abstractor):

      def operation(self, ...):

9         // Perform the appropriate work

          pass


相关模式

Fulfill Method 由两个核心 EDP 组成:Abstract InterfaceInheritance。因为 Fulfill Method 履行了 Abstract Interface 的承诺,所以几乎在所有 Abstract Interface 的情况下都可以找到它。在 Fulfill Method 不附带 Abstract Interface 的情况下,生成的抽象类在子类完成 Promise 之前不能在代码中使用。多个 Fulfill Method 实例可以在本章后面的 Objectifier 下找到。Fulfill Method 也可以作为大多数 Gang of Four 模式的必要组件,包括 ProxyCommandIteratorObserverStateDecoratorPrototypeTemplate Method 等 [21]。

Fulfill Method is composed of two of the core EDPs: Abstract Interface and Inheritance. Because Fulfill Method fulfills the promise made by Abstract Interface, it is found in almost every case where Abstract Interface is. In those cases where Fulfill Method doesn’t accompany Abstract Interface, the resulting abstract class isn’t usable in code until a subclass completes the promise. Multiple Fulfill Method instances can be found later in this chapter under Objectifier. Fulfill Method can also be found as a necessary component of most of the Gang of Four patterns, including Proxy, Command, Iterator, Observer, State, Decorator, Prototype, Template Method, and many others [21].

检索新

Retrieve New

对象管理

Object Management

意图

当需要新实例并且创建该实例是一个复杂或昂贵的过程,需要封装在另一个对象中时使用。保证返回的对象没有任何其他引用。

Used when a new instance is needed and the creation of that instance is a complex or expensive procedure that needs to be encapsulated in another object. The returned object is guaranteed not to have any other references to it.

赋予动机

通常,新创建的对象需要是具有明确定义所有权的原始对象。在本地创建此类对象始终是一种选择,但如果要创建的对象由相当复杂的行为确定,则这可能是一个次优决策。一个例子是根据响应数据库查询而给出的回复来决定创建哪个对象。任何包装在复杂逻辑或事务中的对象创建过程都可能需要大量时间,这些过程都不是适合复制和粘贴重用的代码。我们希望集中这样的 logic 并将其封装到自己的方法中。

Often, a newly created object needs to be a pristine object with well-defined ownership. Creating such an object locally is always an option, but it can be a suboptimal decision if, for instance, the object to be created is determined by a rather complex behavior. An example would be deciding which object to create on the basis of which reply is given in response to a database query. Any object creation process that is wrapped in complex logic or transactions that may take significant time isn’t code that lends itself to copy and paste reuse. We want to both centralize such logic and encapsulate it into its own method.

当然,这种对 logic 的封装会导致其他问题。如果创建的对象可以由多个源引用,并且内存模型是非托管模型(例如在 C++ 中),则对对象具有此类引用的任何代码段都可以请求销毁该对象,从而使其余引用指向无效对象。或者,任何引用对象都不能请求销毁它,从而导致内存泄漏,因为该对象在仍驻留时变得无法访问。垃圾回收可以帮助解决这个问题,但定义明确的内存管理从小决策开始。

This encapsulation of logic leads to other issues, of course. If the created object can be referenced by a number of sources and the memory model is an unmanaged one such as in C++, then any piece of code with such a reference to the object can request its destruction, leaving the remainder of the references pointing to an invalid object. Alternatively, none of the referring objects may request its destruction, leading to a memory leak because the object becomes unreachable while still resident. Garbage collection can help with this issue, but well-defined memory management starts with small decisions.

其中一个决策是确保只有 Retrieve 的接收者才能访问正在检索的对象,从而建立清晰而精确的所有权。这可以通过返回对象的副本来几乎透明地完成,因为该副本将创建一个新实例。可以使用其他方案,该方案涉及传回在方法退出时销毁的方法本地引用,但是在以后的维护中,除非对所涉及的所有权问题有很强的意识和执行力,否则它们更容易出错。在所有情况下,这都需要应用 Create Object 的实例。

One such decision is to make sure that only the recipient of a Retrieve has access to the object being retrieved, thereby establishing a clear and precise ownership. This can be done almost transparently by returning a copy of an object, because the copy will create a fresh instance. Other schemes involving passing back a method-local reference that is destroyed on method exit can be used, but they are more error prone during later maintenance unless there is a strong sense for, and enforcement of, the ownership issues involved. In all cases, this requires an instance of Create Object to be applied as well.

适用性

在以下情况下使用 Retrieve New (检索新项):

Use Retrieve New when:

• 远程对象提供本地计算所需的对象,该对象由公开的字段对象或公开方法的返回值提供。

• A remote object provides an object that is required for local computation and is provided by an exposed field object or return value from an exposed method.

• 返回的对象必须是没有其他引用的新副本。

• The object returned needs to be a fresh copy without other references to it.

结构
图像

仅限 PIN 码的版本(与 Retrieve Shared 相同):

PINbox-only version (identical to Retrieve Shared):

图像
参与者

Source

负责移交检索到的对象的对象的类型。它用作初始源,用于创建对象。

The type of the object tasked with handing off the retrieved object. It serves as the initial source, creating the object.

Sink

请求检索到的对象并包含项 的对象(或类)类型,以便为其提供新值。target

The object (or class) type that requests the retrieved object and includes the item, target, to be given a new value.

检索

Retrieved

需要更新的值的类型和返回的值。

The type of the value to be updated and the value that is returned.

目标

target

更新为检索到的对象以供本地使用的字段。

The field that is updated to the retrieved object for local use.

选择

selected

生成并返回新值的方法或字段。在这种情况下,保证返回的对象没有任何其他引用。

The method or field that produces and returns the new value. In this scenario, the returned object is guaranteed to not have any other references to it.

合作

此协作中只有三个对象,它们只是扮演请求发起者、请求执行者和传递的对象的角色。Retrieve New 的关键部分发生在 retrieval method 的实现中。此方法的任务是确保返回的对象是新形成的,并且没有其他引用。selected

There are only three objects in this collaboration, and they simply play the parts of the request originator, the request fulfiller, and the passed object. The critical portions of Retrieve New occur within the implementation of the retrieval method selected. This method is tasked with ensuring that the object being returned is newly formed and has no other references to it.

后果

对象所有权和对象创建之间的分离有一些优点。例如,可以灵活地确定所创建对象的类型,例如在抽象工厂中,可以使用多态性。但是,这意味着接收方对象仅负责所创建对象的生存期。如果分发了对所创建对象的引用,则必须制定良好的引用管理策略,除非允许垃圾回收。

Separation between object ownership and object creation has some advantages. For example, the type of the created object can be determined in a flexible manner, possibly using polymorphism, such as in an Abstract Factory. It means, however, that the recipient object is solely responsible for the lifetime of the created object. If references to the created object are handed out, a good reference management policy must be in place unless garbage collection is allowed.

实现

在 C++ 中:

In C++:


   检索 {};



2 Source {

4 public:

检索到 giveMeAValue() {

6 检索到 ret;

返回 ret;

8 };

};

10

Sink {

12 检索到目标;

资料来源: srcobj;

14 public:

void operation() {

16 target = srcobj.giveMeAValue();

}

18 };

   class Retrieved {};

 2

   class Source {

 4 public:

       Retrieved giveMeAValue() {

 6         Retrieved ret;

           return ret;

 8     };

   };

10

   class Sink {

12     Retrieved target;

       Source srcobj;

14 public:

       void operation() {

16         target = srcobj.giveMeAValue();

       ;}

18 };


在 Python 中:

In Python:


 1 Source:

def giveMeAValue(self):

3 ret = Retrieved()

return ret

5

class Sink:

7 def __init__(self):

srcobj = Source()

9 return self

11 def operation(self):

target = srcobj.giveMeAValue()

 1 class Source:

       def giveMeAValue(self):

 3         ret = Retrieved()

           return ret

 5

   class Sink:

 7     def __init__(self):

           srcobj = Source()

 9         return self

11     def operation(self):

           target = srcobj.giveMeAValue()


相关模式

Retrieved NewRetrieve Shared 密切相关,如果您查看仅限 PINbox 的图表,它们是等效的。独特部分是行为级方面,因为请求的对象如何返回给调用方,要么具有干净的所有权特征(如此处),要么保留可能的引用(如 Retrieve Shared)。

Retrieved New is closely related to Retrieve Shared, and if you look at the the PINbox-only diagrams, they are equivalent. The distinctive portion is a behavior-level aspect because of how the requested object is returned to the caller, either with clean ownership characteristics, as here, or with possible references being retained, as in Retrieve Shared.

原始设计模式 [21] 中的许多创建模式都以某种方式使用 Retrieve NewPrototype 显式使用 Retrieve New 来确保始终返回对象的新副本,Builder 提供了 Retrieve New 封装复杂创建逻辑的能力的一个很好的示例。在许多情况下,Abstract FactoryFactory Method 最终也使用 Retrieve New

Many of the creational patterns from the original Design Patterns [21] use Retrieve New in some manner. Prototype uses Retrieve New explicitly to ensure that fresh copies of an object are always returned, and Builder provides an excellent example of Retrieve New’s ability to encapsulate complex creation logic. Abstract Factory and Factory Method also end up using Retrieve New in many cases.

检索共享

Retrieve Shared

对象管理

Object Management

意图

获取对共享目标文件的引用,而不持有该目标文件的显式所有权。在面向对象编程中无处不在。

To obtain a reference to a shared object without holding explicit ownership of that object. Ubiquitous in object-oriented programming.

赋予动机

对象是概念实体,任何特定实例都可以封装状态或同时提供许多其他对象可能感兴趣的功能。例如,考虑打印机队列。许多应用程序都希望能够访问队列,但每个应用程序都没有理由拥有自己的队列。事实上,这肯定会导致最终的资源冲突,因为不同的队列会竞争。相反,一个队列可以同时在多个应用程序之间共享。来自不同对象的请求可以按照最适合队列的顺序进行处理。队列是一种共享资源,必须可供任何需要它的人使用。

Objects are conceptual entities, and any particular instance may encapsulate state or provide functionality that may be of interest to many other objects simultaneously. Consider a printer queue, for example. Many applications want to have access to the queue, but there is no reason for each application to have its own queue. In fact, this would certainly lead to eventual resource collisions as differing queues competed. Instead, a queue can be shared among many applications simultaneously. Requests from disparate objects can be handled in the order best suited by the queue. The queue is a shared resource, and must be available for whoever needs it.

Retrieve SharedRetrieve New 同时使用 Create ObjectRetrieve,但不同之处在于,它可以自由缓存或以其他方式移交对新创建的对象的引用以用于其他目的。不仅不能保证新创建的对象没有其他引用,而且明确假定它确实有其他引用。在持有引用的那些实体中,所有权定义不明确。

Retrieve Shared uses both Create Object and Retrieve, as does Retrieve New, but differs in that it is free to cache or otherwise hand off references to the newly created object for other purposes. Not only is there no guarantee that the newly created object has no other references, it is explicitly assumed that it does have other references to it. Ownership is ill-defined among those entities holding references.

Retrieve Shared 非常常见,以至于许多语言在某种程度上直接支持它。例如,C++ 具有该结构,该结构包含在最近采用的 2011 年 C++ 标准 [18] 的第 20.7.2.2 节中。shared_ptr

Retrieve Shared is so common that many languages support it directly on some level. C++, for example, has the shared_ptr construct, which is included in Section 20.7.2.2 of the recently adopted 2011 C++ standard [18].

其他语言和环境具有惯用语和假定的库支持,用于对引用对象的准手动管理,例如 Objective-C 的功能。许多其他组织使用垃圾回收在运行时自动检测并从系统中删除 “死” 对象。Java、C#、Python 和大多数脚本语言都是最后一种情况的示例。自动内存管理在编程语言中变得越来越流行和普遍,但它仍然远未普及。所有开发人员都应该具备手动内存管理的基础知识。autorelease

Other languages and environments have idioms and assumed library support for quasi-manual management of referenced objects, such as Objective-C’s autorelease feature. Many others use garbage collection to automatically detect and remove “dead” objects from the system during runtime. Java, C#, Python, and most scripting languages are examples of this last case. Automated memory management is becoming more popular and common among programming languages, but it is still far from universal. All developers should have the basics of manual memory management under their belt.

适用性

在以下情况下使用 Retrieve Shared (检索共享):

Use Retrieve Shared when:

• 远程对象提供本地计算所需的值对象,并且是:

• A remote object provides a value object that is required for local computation and is either:

– 由方法调用的返回值提供,或者

– provided by a method call’s return value or

—— 由公开的 Field 对象提供。

– provided by an exposed field object.

• 该对象应与其他对象共享,而不是被视为私有资源。

• The object should be shared with other objects and not considered a private resource.

结构
图像

仅限 PINbox 版本(与 Retrieve New 相同):

PINbox only version (identical to Retrieve New):

图像
参与者

Source

负责移交检索到的对象。它用作初始源,用于创建对象。

The object tasked with handing off the retrieved object. It serves as the initial source, creating the object.

Sink

请求检索到的对象并包含项 的对象(或类)类型,以便为其提供新值。target

The object (or class) type that requests the retrieved object and includes the item, target, to be given a new value.

检索

Retrieved

需要更新的值的类型和返回的值。

The type of the value to be updated and the value that is returned.

目标

target

更新为检索到的对象以供本地使用的字段。

The field that is updated to the retrieved object for local use.

选择

selected

生成并返回新值的方法或字段。在这种情况下,返回的对象是对对象的引用,并且不能保证该引用以任何方式与所有权相关联。

The method or field that produces and returns the new value. In this scenario, the returned object is a reference to an object, and the reference is not guaranteed to be associated with ownership in any way.

合作

此协作只有三个对象,它们仅扮演请求发起者、请求执行者和传递的对象的角色。将此模式与 Retrieve New 进行比较和对比。模式元素之间的差异非常小,但效果会迅速向外扩散。

This collaboration has only three objects and they merely play the parts of the request originator, the request fulfiller, and the passed object. Compare and contrast this pattern with Retrieve New. The differences between the elements of the patterns are extremely minor, but the effects can ripple outwards quickly.

后果

在垃圾回收的语言或环境中,检索到的对象使用此模式获得引用计数,但不限制新引用的生成。生成新的引用很容易导致内存泄漏,因此请确保在其他地方存在定义明确的所有权策略。如果需要确保唯一性地检索对象,或者希望成为新分配的共享资源的主要所有者,请考虑使用 Retrieve New

In a garbage-collected language or environment, the retrieved object gains a reference count by using this pattern but does not restrict the generation of new references. Generating new references can lead to memory leaks fairly readily, so be certain a well-defined ownership policy exists elsewhere. Consider Retrieve New instead if an object needs to be retrieved with assured uniqueness or wishes to be the primary owner of a newly allocated shared resource.

实现

在 Java 中:

In Java:


   检索 {

2 };



4 class Source {

public Retrieved giveMeAValue() {

6 Retrieved ret = new Retrieved();

对 ret 做一些事情,比如

8 cache elsewhere

return ret;

10 };

};

12

Sink {

14 检索到目标;

资料来源: srcobj;

16 public void operation() {

target = srcobj.giveMeAValue();

18     };

   };

   class Retrieved {

 2 };



 4 class Source {

       public Retrieved giveMeAValue() {

 6         Retrieved ret = new Retrieved();

           // Do something with ret, such as

 8         // cache elsewhere

           return ret;

10     };

   };

12

   class Sink {

14     Retrieved target;

       Source srcobj;

16     public void operation() {

           target = srcobj.giveMeAValue();

18     };

   };


相关模式

在查看结构图时,Retrieve SharedRetrieve New 显然共享许多相同的概念管道。区别特征是返回对象的所有权的唯一性。

Retrieve Shared and Retrieve New obviously share much of the same conceptual plumbing when looking at the structure diagrams. The distinguishing feature is the uniqueness of the ownership of the returned object.

Singleton 可能是使用 Retrieve Shared 的最常见设计模式。

Singleton is perhaps the most common design pattern that utilizes Retrieve Shared.

对象化器

Objectifier

此规范简要概括了 Walter Zimmer 的 Objectifier 模式,并展示了它是单个较小模式的多重性。此模式的完整定义和原始讨论可在 [43] 中找到。如有必要,请参阅该文本作为基本文件。

This specification briefly recaps Walter Zimmer’s Objectifier pattern and shows how it is a multiplicity of a single smaller pattern. The full definition and original discussion of this pattern can be found in [43]. Please refer to that text for the base document if necessary.

意图

Intent

根据最初的规范,Objectifier 旨在“对象化其他类中的类似行为,以便客户端可以独立于其他行为来改变这些行为”[43,第 363 页]。请注意短语 “similar behaviour in additional classes”。由此,我们可能期望看到某种形式的 Redirection,但 “in additional classes” 限定是一个线索,表明我们可能正在这里处理一个子类化情况。我们从 Fulfill Method 中知道,只要抽象方法和具体方法之间有相似的意图,我们就可以在子类中实现抽象方法。多个具体子类实现行为的方式可能略有不同,但子类之间的总体意图将保持相似。

Objectifier is, according to the original specification, intended to “objectify similar behaviour in additional classes, so that clients can vary such behaviour independently from other behaviour” [43, p. 363]. Note the phrase “similar behaviour in additional classes.” From this we might expect to see some form of Redirection, but the “in additional classes” qualification is a clue that we are probably dealing with a subclassing situation here. We know from Fulfill Method that we can implement an abstract method in a subclass as long as we have similar intent between the abstract and concrete methods. Multiple concrete subclasses may implement the behavior slightly differently, but the overall intent among the subclasses will remain similar.

赋予动机

Motivation

我们在 Zimmer 的原始动机部分发现我们的分析是正确的。“设计中一个常见的问题是抽象与其实现的分离,以及实现的互换”[43,第 363 页]。Zimmer 描述了一个具有抽象接口的单个超类,以及多个提供实现的子类。这告诉我们我们有多个 Fulfill Method 实例,但它们在 Abstractor 角色中都共享一个公共超类。这可以使用堆叠的 PIN 码框以图形方式显示,正如我们在第 3 章第 3.2.4 节中看到的那样。

We find in Zimmer’s original Motivation section that our analysis was correct. “A frequent problem in design is the separation of an abstraction from its implementation, and the interchange of implementations” [43, p. 363]. Zimmer is describing a single superclass with an Abstract Interface, and multiple subclasses that provide implementations. This tells us that we have multiple Fulfill Method instances but that they all share a common super-class in the Abstractor role. This can be shown graphically using a stacked PINbox, as we saw back in Chapter 3, Section 3.2.4.

Zimmer 向上述子类实现集合添加了一个关键部分。他指出,“为了客观化不同的行为,[......]具有可以在运行时互换的独立实现对象“[第 363 页]。他的意思是,开发人员应该创建一个对类型为该类的对象的引用,然后根据需要交换该子类的实际实例。现在,对该实例进行的所有调用都将通过多态性以不可见的方式自动发送到正确的实现。如何拨打电话并不是特别相关;重要的是,某些 Client Object 可以执行此操作。Abstractor

Zimmer adds one critical piece to the above collection of subclass implementations. He specifies that “to objectify the varying behaviour, [...] have independent implementation objects that can be interchanged at runtime” [p. 363]. By this he means that the developer should create a reference to an object typed to be of the Abstractor class and then swap out actual instances of the subclass as needed. All calls made to that instance will now invisibly and automatically get sent to the correct implementation via polymorphism. How the call is made is not particularly relevant; the important thing is that some client object can do so.

适用性

Applicability

Zimmer 对 Objectifier 适用性的描述几乎没有什么可补充的,所以我在这里完整引用它:

There is little to add to Zimmer’s description of the applicability of Objectifier, so I quote it here in full:

在以下情况下使用 Objectifier 模式

Use the Objectifier pattern when

• 行为应与类解耦,以具有独立的行为对象,这些对象可以交换、保存、更改、共享或调用。

• Behaviour should be decoupled from classes to have independent behaviour objects which can be interchanged, saved, changed, shared or invoked.

• 需要运行时配置行为。

• Run-time configuration of behaviour is required.

• 有几个几乎相同的类,它们仅在一个或几个方法上有所不同。将附加类中的不同行为对象化允许将以前的类统一在一个公共类中,然后可以使用对新的附加类的引用来配置该类

• There are several almost identical classes that differ only in one or a few methods. Objectifying the different behaviour in additional classes allows to unify the former classes in one common class, which can then be configured with a reference to the new, additional classes

• 有大量的条件代码来选择行为 [43, p. 363]。

• There is a large amount of conditional code to select behaviour [43, p. 363].

在决定何时使用 Objectifier 时,有两个项目很突出。首先,运行时配置是必需的,其次,有大量的条件代码来选择行为。

Two items stand out here in deciding when to use Objectifier. First, that run time configuration is required, and second, that there is a large amount of conditional code to select behavior.

任何时候你遇到类似 Listing 6.1 的情况,它都应该让你停下来考虑使用 Objectifier。如果您发现多个使用相同触发器的此类条件结构,那么这是一个强烈的迹象,表明您可能能够将条件块重构为具有公共超类的类以提供接口。然后,条件块被替换为对请求方法的简单调用,如清单 6.2 所示。这看起来应该有点熟悉:它与第 5 章 Abstract Interface 规范中提供的示例有关。

Any time you run across a situation that looks something like Listing 6.1, it should make you pause to consider using Objectifier. If you find multiple such conditional structures using the same triggers, then it is a strong sign that you may be able to refactor the conditional blocks into classes with a common superclass to provide an interface. The conditional blocks are then replaced with a simple invocation of the requested method, as in Listing 6.2. This should look a bit familiar: it is related to the example provided back in the specification for Abstract Interface in Chapter 5.

请注意,外部接口 to 没有更改,但下面的代码现在更加简洁。添加新的动物类型只需将其定义为 的子类,并在代码中的某个点进行更改,其中动物种类在 中选择。以前,添加新的 animal 类型需要向它可能适用的每个位置添加条件块,这些块分散在整个代码中。feedCritterInEnvirons()AnimalfeedCritterInEnvirons()

Note that the external interface to feedCritterInEnvirons() did not change, but the code underneath is now much cleaner. Adding a new animal type just requires defining it as a subclass of Animal and making a change at one point in the code, where the animal kind is being selected in feedCritterInEnvirons(). Previously, adding a new animal type would have required adding conditional blocks to each place that it might be applicable, scattered throughout the code.

清单 6.1.用于选择行为的条件语句。

Listing 6.1. Conditionals to select behavior.


 

1 typedef enum{FISH, HORSE, CHEETAH} CritterKind;

3 小动物种类的小动物;



5 void eatFood(CritterKind critter, FoodItem f) {

if (critter == FISH) {

7 swimToFood(critter, locationOfFood(f));

ingest(小动物,f);

9 摘要(小动物,f);

} else if (critter == HORSE) {

11 checkHerdSafety(critter);

ingest(小动物,f);

13 消化(小动物,f);

} else if (critter == CHEETAH) {

15 turboMode(critter, locationOfFood(f));

if (caughtPrey(f)) {

17 ingest(critter, f);

摘要(Critter, F);

19 }

} else {

21 ingest(critter, f);

摘要(Critter, F);

23 }

};

25

void ingest(CritterKind critter, FoodItem f) {

27 if (critter == FISH) {

...

29 } else if (critter == 马) {

...

31 } else if (critter == 猎豹) {

...

33 } else {

...

35 }

};

37

typedef enum {OCEAN, PLAINS, SAVANNAH} Environment;

39

void feedCritterInEnvirons(Environment env) {

41 if (Environment == OCEAN) {

if (isHungry(FISH)) {

43 FoodItem f = findFood(FISH);

如果 (f) {

45 eatFood(FISH, f);

}

47 }

} else if (Environment == PLAINS) {

49 if (isHungry(HORSE)) {

FoodItem f = findFood(HORSE);

51 if (f) {

eatFood(HORSE, f);

53 }

}

55 } else if (Environment == SAVANNAH) {

if (isHungry(CHEETAH)) {

57 FoodItem f = findFood(CHEETAH);

if (f) {

59 eatFood(猎豹, f);

}

61 }

}

63 };

 1

   typedef enum{FISH, HORSE, CHEETAH} CritterKind;

 3 CritterKind critter;



 5 void eatFood(CritterKind critter, FoodItem f) {

       if (critter == FISH) {

 7         swimToFood(critter, locationOfFood(f));

           ingest(critter, f);

 9         digest(critter, f);

       } else if (critter == HORSE) {

11         checkHerdSafety(critter);

           ingest(critter, f);

13         digest(critter, f);

       } else if (critter == CHEETAH) {

15         turboMode(critter, locationOfFood(f));

           if (caughtPrey(f)) {

17             ingest(critter, f);

               digest(critter, f);

19         }

       } else {

21         ingest(critter, f);

           digest(critter, f);

23     }

   };

25

   void ingest(CritterKind critter, FoodItem f) {

27    if (critter == FISH) {

          ...

29    } else if (critter == HORSE) {

          ...

31    } else if (critter == CHEETAH) {

          ...

33    } else {

          ...

35    }

   };

37

   typedef enum {OCEAN, PLAINS, SAVANNAH} Environment;

39

   void feedCritterInEnvirons(Environment env) {

41     if (Environment == OCEAN) {

           if (isHungry(FISH)) {

43             FoodItem f = findFood(FISH);

               if (f) {

45                 eatFood(FISH, f);

               }

47         }

       } else if (Environment == PLAINS) {

49         if (isHungry(HORSE)) {

               FoodItem f = findFood(HORSE);

51             if (f) {

                   eatFood(HORSE, f);

53             }

           }

55     } else if (Environment == SAVANNAH) {

           if (isHungry(CHEETAH)) {

57             FoodItem f = findFood(CHEETAH);

               if (f) {

59                 eatFood(CHEETAH, f);

               }

61         }

       }

63 };


清单 6.2.使用 Objectifier 选择行为。

Listing 6.2. Using Objectifier to select behavior.


 1 动物 {

public:

3 void eatFood(FoodItem f) {

this->ingest(f);

5 这个>digest(f);

};

7 void ingest(FoodItem f);

};



9 鱼 : public 动物 {

11 public:

void eatFood(FoodItem f) {

13 this->swimToFood(locationOfFood(f));

this->ingest(f);

15 >文摘(f);

};

17 void ingest(FoodItem f);

};

19

马 : public 动物 {

21 public:

void eatFood(FoodItem f) {

23 checkHerdSafety(critter);

ingest(小动物,f);

25 消化(critter, f);

};

27 void ingest(FoodItem f);

};

29

猎豹 : public 动物 {

31 public:

void eatFood(FoodItem f) {

33 turboMode(critter, locationOfFood(f));

if (caughtPrey(f)) {

35 ingest(critter, f);

摘要(Critter, F);

37 }

};

39 };



41 typedef enum {OCEAN, PLAINS, SAVANNAH} Environment;



43 void feedCritterInEnvirons(Environment env) {

Animal* 小动物;

45 if (Environment == OCEAN) {

critter = new Fish();

47 } else if (Environment == PLAINS) {

critter = new Horse();

49 } else if (Environment == SAVANNAH) {

critter = new Cheetah();

51 }

if (isHungry(critter)) {

53 FoodItem f = findFood(critter);

if (f) {

55 eatFood(小动物, f);

}

57     }

   };

 1 class Animal {

   public:

 3     void eatFood(FoodItem f) {

           this->ingest(f);

 5         this->digest(f);

       };

 7     void ingest(FoodItem f);

   };

 9

   class Fish : public Animal {

11 public:

       void eatFood(FoodItem f) {

13         this->swimToFood(locationOfFood(f));

           this->ingest(f);

15         this->digest(f);

       };

17     void ingest(FoodItem f);

   };

19

   class Horse : public Animal {

21 public:

       void eatFood(FoodItem f) {

23         checkHerdSafety(critter);

           ingest(critter, f);

25         digest(critter, f);

       };

27     void ingest(FoodItem f);

   };

29

   class Cheetah : public Animal {

31 public:

       void eatFood(FoodItem f) {

33         turboMode(critter, locationOfFood(f));

           if (caughtPrey(f)) {

35             ingest(critter, f);

               digest(critter, f);

37         }

       };

39 };



41 typedef enum {OCEAN, PLAINS, SAVANNAH} Environment;



43 void feedCritterInEnvirons(Environment env) {

       Animal* critter;

45     if (Environment == OCEAN) {

           critter = new Fish();

47     } else if (Environment == PLAINS) {

           critter = new Horse();

49     } else if (Environment == SAVANNAH) {

           critter = new Cheetah();

51     }

       if (isHungry(critter)) {

53         FoodItem f = findFood(critter);

           if (f) {

55             eatFood(critter, f);

           }

57     }

   };


结构

Structure

图像

仅限 PINbox 版本:

PINbox-only version:

图像

参与者

Participants

我们可以将以下内容添加到 Zimmer 提供的参与者中:

We can add the following to the participants that Zimmer provides:

操作

operation

客户端正在抽象出并请求的方法。

The method that is being abstracted out and requested by the client.

Concrete 类

Concrete Class

提供 implementation 的子类之一。

One of the subclasses providing an implementation.

合作

Collaborations

Zimmer 的简短讨论没有什么可补充的,所以我在这里完整地引用它。

There is little to add to Zimmer’s brief discussion, so I quote it here in its entirety.

• 客户端可以使用 Objectifier 来委托其部分行为。Objectifier 在初始化期间接收完成其任务所需的信息,或者客户端在调用 Objectifier 时将信息作为参数传递。

• A client may use Objectifier to delegate parts of its behaviour. The Objectifier receives the information needed to fulfill its task during its initialization, or the client passes the information as a parameter when calling the Objectifier.

• 客户可以配置一个具体的 Objectifier 来调整行为以完成特定任务 [43, p. 364]。

• A client can be configured with a concrete Objectifier to adapt the behaviour to fulfil a certain task [43, p. 364].

后果

Consequences

总的来说,这种模式非常有用。它在大多数系统中提供了很大程度的灵活性和可扩展性,同时增强了代码正文的可理解性和清晰度。但是,如果行为意图没有明确说明或与类层次结构中不相关的行为交织在一起,则可能会导致混淆。请注意,您希望在 Objectifier 类族中捆绑在一起的方法的意图是兼容且清晰的。

Overall, this pattern is highly useful. It offers a great degree of flexibility and extensibility in most systems, while enhancing the comprehensibility and clarity of a body of code. It can, however, lead to confusion if the intent of behavior is not explicitly stated or is intertwined with unrelated behaviors within a class hierarchy. Be careful that the intents of methods you wish to bundle together in an Objectifier class family are compatible and clear.

实现

Implementation

任何支持 Fulfill Method 的语言都可以支持 Objectifier。这种模式通常出现在大多数使用多态类结构的现代软件中。

Any language that supports Fulfill Method can support Objectifier. This pattern is typically found in most modern software that uses a class structure polymorphically.

相关模式

Related Patterns

Zimmer 列举了许多使用 Objectifier 的 Gang of Four 模式,包括 BridgeBuilderCommandIteratorObserverStateStrategy。对于这些,我们可以添加 Chain of ResponsibilityCompositeDecorator,通过在下一个模式(Object Recursion)中存在 Objectifier

Zimmer enumerates many Gang of Four patterns that use Objectifier, including Bridge, Builder, Command, Iterator, Observer, State, and Strategy. To these we can add Chain of Responsibility, Composite, and Decorator, through the existence of Objectifier in the next pattern, Object Recursion.

对象递归

Object Recursion

此规范简要回顾了 Bobby Woolf 的 Object Recursion,并演示了它是两个较小模式的简单交集。此模式的完整定义和原始讨论可以在 Woolf 的 “The Object Recursion Pattern” [41] 中找到。如有必要,请参阅该文本作为基本文件。

This specification briefly recaps Bobby Woolf’s Object Recursion and demonstrates that it is a simple intersection of two smaller patterns. The full definition and original discussion of this pattern can be found in Woolf’s “The Object Recursion Pattern” [41]. Please refer to that text for the base document if necessary.

意图

Intent

Booby Woolf 将意图陈述如下:“通过多态委托,将请求的处理分散到结构上。对象递归透明地允许将请求重复分解为更易于处理的较小部分“[41,第 41 页]。然而,这并没有说明那些较小的部分,从我们对 DelegationRedirection 的讨论中,我们知道行为的子任务之间的关系可以显着影响系统的实现方式。在阅读了整个规范之后,我们可以补充一下,这些 “较小的部分” 在意图和行为上密切相关。

Booby Woolf states the intent as follows: “Distribute processing of a request over a structure by delegating polymorphically. Object Recursion transparently enables a request to be repeatedly broken into smaller parts that are easier to handle” [41, p. 41]. This says nothing about those smaller parts, however, and from our discussions of Delegation and Redirection, we know that the relationship between subtasks of a behavior can significantly affect how a system is implemented. After reading the entire specification, we can add that these “smaller parts” are closely related in intent and behavior.

赋予动机

Motivation

在最初的规范中,Woolf 为这种比较两个对象的模式使用了一个驱动示例,让它们告诉各自的成员比较自己,递归地沿着对象树工作。这是一个很好的用例,它说明了 Object Recursion 的一个必要组成部分:在每个级别执行的任务都是相同类型的工作。这是一个重定向的示例,因此我们希望在 Object Recursion 的任何分解中找到某种形式的 Redirection

In the original specification, Woolf used a driving example for this pattern of comparing two objects by having them tell their respective members to compare themselves, recursively working down an object tree. This is a good use case and illustrates a necessary component of Object Recursion: that the task being performed at each level is the same kind of work. This is an example of redirection, so we expect to find some form of Redirection in any decomposition of Object Recursion.

在 Intent 部分使用多态性一词向我们表明了两件事。首先,它告诉我们,我们将看到至少一个 Fulfill Method 实例以及底层的 InheritanceAbstract Method EDP。其次,它告诉我们几乎可以肯定会有多个这样的实例,即每个潜在的子类目标都有一个实例。因此,我们期望看到 Objectifier 的实例。

The use of the word polymorphism in the Intent section indicates two things to us. First, it tells us that we’re going to see at least one instance of Fulfill Method as well as the underlying Inheritance and Abstract Method EDPs. Second, it tells us that there almost certainly will be more than one such instance, that is, one for each potential subclass target. We therefore expect to see an instance of Objectifier.

在这一点上,我们有两个定义明确且简单的概念,我们可以使用它们来帮助我们理解 Object Recursion 模式。我们只需要看看在我们继续分析这种模式时如何改进它们,以及它们如何组合在一起形成这个更复杂的行为和概念。

At this point, we have a duo of well-defined and simple concepts that we can use to help us understand the Object Recursion pattern. We just need to see how they might be refined as we continue analyzing this pattern and how they come together to form this more complex behavior and concept.

适用性

Applicability

Woolf 原始规范中的适用性部分指出,对象递归适用于以下情况:

The Applicability section in Woolf’s original specification states that Object Recursion is appropriate when:

• 通过最终目的地未知的链接结构传递消息。

• passing a message through a linked structure where the ultimate destination is unknown.

• 将消息广播到链接结构一部分的所有节点。

• broadcasting a message to all nodes in part of a linked structure.

• 在整个链接结构中分配行为的责任 [41, p. 44]。

• distributing a behavior’s responsibility throughout a linked structure [41, p. 44].

有趣的是,如果我们将适用性限制为这些约束,则无需使用 Woolf 提供的解决方案即可解决问题。具体来说,适用性中没有任何内容涉及分布式行为的相对意图或对象在执行该行为时可以相互信任的级别。如前所述,上述约束可以很容易地由一系列不相关的对象通过使用 Redirection,或者通过对条件进行一些创造性的解释,Delegation

It’s interesting to note that if we were to limit the applicability to only these constraints, the problem would be solvable without using the solution that Woolf provided. Specifically, nothing in the applicability addresses the relative intents of the behavior that is distributed or the level of trust that the objects can place in each other to perform that behavior. The above constraints, as stated, could just as easily be handled by a chain of unrelated objects through uses of Redirection, or, with a bit of creative interpretation of the criteria, Delegation.

从 Intent 和 Motivation 部分的信息中,我们可以推断出 Object Recursion 的其他一些关键特征,并增强本节内容。例如,我们知道多态性将占主导地位,并且前面描述的 “链接结构” 可能有一些非常具体的要求,主要是链接结构由其类型通过共同祖先相互关联的对象组成,正如 意图 部分中提到的多态性所证明的那样。原始规范中后面的章节证实了这一假设。

From the information in the Intent and Motivation sections, we can deduce some other key characteristics of Object Recursion and enhance this section. For instance, we know that polymorphism will figure prominently and that the “linked structure” described previously likely has some very specific requirements, primarily that the linked structure is composed of objects whose types are related to one another via a common ancestor, as evidenced by the polymorphism mentioned in the Intent section. This assumption is borne out by the later sections in the original specification.

这个共同的祖先线索让我们期望看到 Redirection 的子类型关系形式。换句话说,我们可以指定我们期望看到 Trusted Redirection,而不是广义重定向

This common ancestor clue leads us to expect to see the subtyping-relation form of our Redirection. In other words, we can specify that instead of a generalized Redirection, we will expect to see Trusted Redirection.

使用此信息,我们可以按如下方式优化适用性:

Using this information, we can refine the applicability as follows:

• 存在链接结构 (如树),并且结构中的元素具有共同的意图和用途,每个元素都有共同定义的行为。

• A linked structure such as a tree exists, and the elements in the structure have a shared intent and purpose, each with common defined behaviors.

• 需要通过此链接结构发送消息以调用其中一种常见行为;在传递过程中,消息的意图需要基本保持不变。

• A message needs to be sent through this linked structure to invoke one of the common behaviors; the intent of the message needs to remain essentially unaltered in intent during passage.

• 在链接结构中,传递消息的最终目的地是未知的。

• The ultimate destination of the passed message is unknown within the linked structure.

• 可能需要将消息广播到链接结构的一部分中的所有节点。

• The message may need to be broadcast to all nodes in just a portion of the linked structure.

• 完成消息请求的行为的责任分布在整个链接结构中。

• Responsibility for fulfilling the behavior requested by the message is distributed throughout the linked structure.

现在很明显,链接结构不包含随机元素,但结构中的每个节点在性质和意图上都是相关的。还明确指出,可以调用常见行为,行为可以通过结构中的对象进行分发,和/或行为可能适用于也可能不适用于整个结构。所有这些要点对于塑造解决方案的最终外观都很重要。

Now it is clear that the linked structure does not contain random elements, but each node in the structure is related in nature and intent. It is also clearly stated that common behaviors can be invoked, that behavior can be distributed through objects in the structure, and/or that the behavior may or may not be applicable to the entire structure. All of these points are important for shaping what the solution will eventually look like.

结构

Structure

我们可以在检查 Woolf 提供的原始 UML 图时使用这些推导,并用 PIN 框对其进行注释,如图所示。正如我们推测的那样,ObjectifierTrusted Redirection 实例确实存在。

We can use these deductions during an inspection of the original UML diagram provided by Woolf and annotate it with PINboxes, as shown. The Objectifier and Trusted Redirection instances do exist, as we surmised.

参与者

Participants

Woolf 的原始规范列出了四个参与者:Initiator、Handler、Recursor 和 Terminator。对于这些,我们可以为结构 PIN 图再添加两个,并在讨论中清晰地添加: handleRequest 和 successor。然后,参与者可以列为:

Woolf’s original specification lists four participants: Initiator, Handler, Recursor, and Terminator. To these, we can add two more for the structure PIN diagrams and for clarity in the discussion: handleRequest and successor. The participants then can be listed as:

引发

Initiator

– 发起请求。

– initiates the request.

– 通常不是 ; 是独立于 的消息。HandlermakeRequest()handleRequest()

– usually not a subtype of Handler; makeRequest() is a separate message from handleRequest().

处理器

Handler

– 定义可以处理发起方发出的请求的类型。

– defines a type that can handle requests that initiators make.

Recursor (解析器)

Recursor

– 定义链接。successor

– defines the successor link.

– 通过将请求委托给其后续请求来处理请求。

– handles a request by delegating it to its successors.

图像

我们可以将其简化为更具可读性的仅限 PIN 框的版本:

We can reduce this down to a much more readable PINbox-only version:

图像

– 与请求相关的后继者可能因请求而异。

– successors relevant to a request can vary by request.

– 可以在委托请求之前或之后执行额外的行为。

– can perform extra behavior before or after delegating the request.

– 可能是不同请求的终止符。

– may be a terminator for a different request.

终结者

Terminator

—— 通过完全实现请求而不是委托任何实现来完成请求。

– finishes the request by implementing it completely and not delegating any of its implementation.

– 可能是不同请求的重新解析器 [41, p. 44]。

– may be a recursor for a different request [41, p. 44].

handleRequest 请求

handleRequest

在每个阶段处理请求的行为并负责根据需要将其传递给后续程序的方法。

The method that handles the requested behavior at each stage and is responsible for passing it along to a successor as necessary.

接班人

successor

从消息的实例接收消息的对象为 type .handleRequestRecursor;Handler

The object that receives the handleRequest message from an instance of Recursor; it is of type Handler.

合作

Collaborations

将 PINbox 添加到原始 UML 序列图中,让我们可以看到在实现对象递归行为时 ObjectifierTrusted Redirection 的责任之间的确切界限。每个 S 都占据了序列图的一半,它们在对象中相遇。aRecursor

Adding PINboxes to the original UML sequence diagram lets us see the exact demarcation between the responsibilities of Objectifier and Trusted Redirection in implementing the behavior of Object Recursion. Each is taking half of the sequence diagram, and they meet in the aRecursor object.

图像

后果

Consequences

Woolf 在 Consequences 下列出的一个缺点是:“编程复杂性。递归,无论是过程的还是面向对象的,都是一个难以理解的概念。过度使用会使系统更难理解和维护“[41,第 46 页]。虽然这是真的,但可以通过 Objectifier 将这种递归风格的两个基本元素划分为多态结构,并通过 Trusted Redirection 将递归式调用划分为该多态系统,从而阐明这种情况。现在,这种分布式递归技术的两个方面被明确地显示了出来,有助于理解。

The one disadvantage that Woolf lists under Consequences is: “Programming complexity. Recursion, procedural or object-oriented, is a difficult concept to grasp. Overuse can make a system more difficult to understand and maintain” [41, p. 46]. While true, the situation can be clarified by dividing the two essential elements of this style of recursion into the structure of the polymorphism via Objectifier and the recursive-style call into that polymorphic system via Trusted Redirection. Now the two aspects of this distributed recursion technique are explicitly shown, aiding comprehension.

实现

Implementation

最初的对象递归规范在其 Implementation 部分指出,“Initiator.makeRequest() 消息不能与 Recursor.handleRequest() 消息多态”[41,第 47 页]。前面的讨论(其中明确了 ObjectifierTrusted Redirection 实例)清楚地显示了这种区别。

The original Object Recursion specification states in its Implementation section that “The Initiator.makeRequest() message must not be polymorphic with the Recursor.handleRequest() message” [41, p. 47]. The earlier discussion, where the Objectifier and Trusted Redirection instances are made explicit, shows this distinction clearly.

相关模式

Related Patterns

对象递归通常用于具有系列类集群结构的设计模式,包括 Chain of ResponsibilityDecoratorCompositeInterpreterAbstract Factory 可以使用 Object Recursion 来启用多层对象的构建,每个子组件都根据需要通过工厂接口请求适当的回调。

Object Recursion is commonly used in design patterns with a family class cluster structure, including Chain of Responsibility, Decorator, Composite, and Interpreter. Abstract Factory can use Object Recursion to enable the building of multilayered objects, with each subcomponent requesting an appropriate callback through the factory interface as needed.

7. 四人组模式构图

7. Gang of Four Pattern Compositions

此时,您已经看到了完整的方法调用 EDP 目录以及通过组合它们形成的模式的几个示例。在本章中,我们将该过程提升到一个新的水平,展示如何将第 2 章和第 6 章中的模式组合起来,形成开发人员最熟悉的四人组 (GoF) 设计模式中的一些模式。

At this point, you have seen the full method-call EDP catalog and several examples of patterns that are formed by combining them. In this chapter, we take that process to the next level, showing how the patterns in Chapters 2 and 6 can be combined to form a few of the Gang of Four (GoF) design patterns that developers are most familiar with.

本章讨论了 6 种 GoF 模式,分别来自 Design Patterns 中定义的 Creational、Structural 和 Behavioral 系列。这些附加模式为在此级别上使用其他设计模式提供了基础。为什么只有 6 个?尽管每种设计模式都可以用 EDP 来描述,但并非所有设计模式都可以仅使用本书中介绍的 EDP 来充分描述。请记住,本书只介绍基于方法调用的 EDP,而方法调用只是面向对象编程的实体之间的四种依赖关系中的一种。请参阅第 2.2.2 节中的第 2 章2.3 以刷新其余部分。之所以选择这六个人,是因为他们与群体中的其他人有着很好的关系,乍一看并不明显,但一旦揭示了潜在的概念结构,这些关系就会变得明显。

Six GoF patterns are discussed in this chapter, two each from the Creational, Structural and Behavioral families as defined in Design Patterns. These additional patterns provide a foundation for working with other design patterns on this level. Why only six? Although every design pattern can be described in terms of EDPs, not all design patterns can be adequately described using only the EDPs presented in this book. Remember that this book only covers EDPs based on method calls, and method calls are only one of the four kinds of reliance relationships among entities of object-oriented programming. Refer back to Chapter 2, Table 2.3, in Section 2.2.2 for a refresh on the remainder. These six were selected because they are nicely related to others in the group in ways that are not obvious at first glance but become apparent once the underlying conceptual structure is revealed.

本章中的讨论不遵循前两章使用的模式规范模板。这些设计模式在许多来源中都有很好的记录,这里没有必要复制结构。相反,讨论以更随意的方式概述了每个设计模式和组合。当然,PIN 和 UML 图用于说明相关的关系和连接。

The discussions in this chapter do not follow the pattern specification template that the previous two chapters used. These design patterns are well documented in numerous sources, and there is no need to duplicate the structure here. Instead, the discussions outline each design pattern and the composition in a more casual way. PIN and UML diagrams, of course, are used to illustrate relevant relationships and connections.

7.1. 创建模式

7.1. Creational Patterns

创建模式都与对象的创建和管理有关。如您所料,Create Object 在其定义中占有重要地位,与 Retrieve 相关的模式也是如此。这两个 EDP 如何相互连接以及与其他 EDP 的连接会产生各种各样的行为,从而将创建模式彼此区分开来。我们分解和比较的两个是 Abstract FactoryFactory Method。它们提供了一个有趣的提醒,即构成的微小差异如何导致实现和意图的重大变化。

The creational patterns are all concerned with object creation and management. As you might expect, Create Object features heavily in their definitions, as do the Retrieve-related patterns. How these two EDPs are connected to each other and to the other EDPs creates a wide array behaviors that distinguish the Creational patterns from each other. The two we decompose and compare are Abstract Factory and Factory Method. They provide an interesting reminder of how small differences in composition can lead to large changes in implementation and intent.

7.1.1. 抽象工厂

7.1.1. Abstract Factory

第一个创建模式,抽象工厂,是一个完美的例子,说明简单的概念如何以复杂的方式关联以产生一个有趣的概念。只需要三种基本模式即可捕获 Abstract Factory 的大部分语义:Fulfill MethodRetrieveCreate Object。他们连接的方式建立了丰富的行为。

The first creational pattern, Abstract Factory, is a perfect example of how simple concepts can relate in complex ways to produce an interesting concept. Only three basic patterns are needed to capture most of the semantics of Abstract Factory: Fulfill Method, Retrieve, and Create Object. The manner in which they connect establishes the rich behavior.

Abstract Factory 的意图是它“提供了一个接口,用于创建相关或依赖对象的系列,而无需指定它们的具体类”[21,第 87 页]。从这个陈述中,我们可以推断出我们很可能会看到使用 Inheritance 来生成该接口,并使用 Fulfill Method 来提供相关方法的实现。

The intent for Abstract Factory states that it “provides an interface for creating families of related or dependent objects without specifying their concrete classes” [21, p. 87]. From this statement, we can deduce that we are likely to see uses of Inheritance to produce that interface and of Fulfill Method to provide the implementation of the relevant methods.

图 7.1 显示了包装在扩展 PIN 框中的 Abstract Factory 的结构图。PINbox 边框中的每个角色都连接到一个通向 Abstract Factory 的元素互连:中实现的方法用于生成具体类的实例,该实例是从类中子类化而来的。其他三个商品类别也存在类似的联系。createProductA()ConcreteFactory2ProductA2AbstractProductA

Figure 7.1 shows the structure diagram for Abstract Factory wrapped within an expanded PINbox. Each role in the PINbox border is connected to just one interconnection of elements that lead to Abstract Factory: the createProductA() method as implemented in ConcreteFactory2 is used to produce an instance of the ProductA2 concrete class, which is subclassed from the AbstractProductA class. Similar connections exist for the other three product classes.

图像

图 7.1.抽象 Factory 包含在扩展的 PIN 框中。

Figure 7.1. Abstract Factory subsumed within the expanded PINbox.

这是一个相当复杂的图表,很难确切地看出工作中的关系是什么。图 7.2 中更清楚地显示了单一的互连,其中我们删除了结构图不直接参与这个单一抽象工厂实例的元素。图 7.3 通过稍微清理 PINbox 的内部结构进一步简化了它,最后我们来到了图 7.4,它说明了 Abstract Factory 核心的简单概念结构。

This is a rather complex diagram, and it is difficult to see exactly what the relationships at work are. The single interconnection is shown more clearly in Figure 7.2, in which we have removed the elements of the structure diagram that are not directly involved in this single Abstract Factory instance. Figure 7.3 simplifies it further by cleaning up the internals of the PINbox a bit, leading us finally to Figure 7.4, which illustrates the simple conceptual structure at the core of Abstract Factory.

图像

图 7.2.将关系图简化为仅 Abstract Factory 的一个实例。

Figure 7.2. Reducing the diagram to just one instance of Abstract Factory.

图像

图 7.3.简化图 7.2.

Figure 7.3. Simplifying Figure 7.2.

图像

图 7.4.仅将 Abstract Factory 作为 PIN。

Figure 7.4. Abstract Factory as PIN only.

此时,我们可以看到一个相当简单的连接在这里起作用。RetrieveCreate Object 返回一个实例,并通过调用 Fulfill Method 在子类中实现的抽象方法来执行此操作。虽然看起来我们可以简单地将 RetrieveCreate Object 的实例折叠成一个 Retrieve New,但在某些情况下,Abstract Factory 可能会使用诸如 Flyweight 之类的模式来为其返回的对象提供节省空间的支持,从而导致 Retrieve Shared 被实现。还要记住,这两种形式的 Retrieve 具有相同的 PIN 图,因此我们无法仅凭此基础来区分它们。由于我们无法提前确定这种情况,而应留给设计人员或实现者,因此我们此时将两个 EDP 保持可见,以便清晰和灵活。

At this point, we can see that a fairly simple connection is at work here. Retrieve is returning an instance from Create Object and is doing so through a call to an abstract method that is being implemented in a subclass via Fulfill Method. Although it may look like we could simply collapse the instances of Retrieve and Create Object into a Retrieve New, in some situations, Abstract Factory may use a pattern such as Flyweight for space-efficient backing of the objects it is returning, resulting in Retrieve Shared being implemented instead. Remember also that these two forms of Retrieve have identical PIN diagrams, so we can’t distinguish between them on this basis alone. Because we can’t determine this situation ahead of time but should leave up to the designer or implementor, we keep the two EDPs visible at this point for clarity and flexibility.

7.1.2. Factory 方法

7.1.2. Factory Method

此模式将实际对象的创建推迟到子类,同时提供用于创建对象并在内部使用它的 solid 接口和基本方法。在开始分析之前,我们认识到,由于这是一种创建模式,因此我们希望看到 Create ObjectRetrieve 都在使用。从 Factory Method 的 Participants 部分中读取项目,为我们提供了继续分解所需的提示。例如,对 Creator 的描述指出它“声明了工厂方法”[21,第 108 页],而 ConcreteCreator “覆盖了工厂方法”。这是 Fulfill 方法的一个明显示例。此外,还指出 Creator “可以调用工厂方法”。这里有一些潜台词,但我们可以通过一些思考来弄清楚这对我们的分析意味着什么。如果 Creator 正在调用它声明的工厂方法,则它只能通过以下两种方式之一执行此操作。工厂方法正在调用自身,或者工厂方法正在从另一个方法调用。前者是递归的一个例子,它足够重要,如果它是 Factory Method 合作的一部分,它几乎肯定会被提到。这让我们相信工厂方法是从另一个方法(作为 Conglomeration 的实例)调用的。检查 Factory Method 的结构 UML 图(图 7.5)确认意图是后一种情况。

This pattern defers creation of an actual object to a subclass while providing a firm interface and a base method for creating the object and using it internally. To begin our analysis, we recognize that because this is a Creational pattern, we expect to see both Create Object and Retrieve in use. Reading off the items from Factory Method’s Participants section gives us the necessary cues to continue the decomposition. For instance, the description for Creator states that it “declares the factory method” [21, p. 108], whereas ConcreteCreator “override[s] the factory method.” This is a clear example of a Fulfill Method. It is also indicated that Creator “may call the factory method.” There’s a bit of subtext here, but we can figure out what this means for our analysis with a bit of thought. If Creator is calling the factory method that it declares, it can only do so in one of two ways. Either the factory method is calling itself or the factory method is being called from another method. The former is an example of Recursion and is important enough that it almost certainly would be mentioned by name if it were part of the collaborations of Factory Method. This leads us to believe that the factory method is being called from another method, as an instance of Conglomeration. Inspecting the structure UML diagram for Factory Method (Figure 7.5) confirms that the intent is the latter case.

图像

图 7.5.Factory Method 包含在展开的 PIN 框中。

Figure 7.5. Factory Method subsumed within the expanded PINbox.

Factory Method 将 EDP Create ObjectRetrieveFulfill MethodConglomeration 编织在一个类型层次结构中,如图 7.5 所示。通过将其简化为图 7.6 中的仅 PIN 图,可以使这种结构更加清晰。

Factory Method weaves the EDPs Create Object, Retrieve, Fulfill Method, and Conglomeration together within one type hierarchy, as shown in Figure 7.5. This structure can be made clearer by reducing it to the PIN-only diagram in Figure 7.6.

图像

图 7.6.Factory Method 仅作为 PIN。

Figure 7.6. Factory Method as PIN only.

Factory Method 是一个有趣的示例,因为它是一个 GoF 模式,在其定义中显式使用另一个 GoF 模式。在他们对工厂方法的讨论中,GoF 提到“工厂方法通常在模板方法中调用”[21,第 116 页]。现在,我们对它们如何交互有了清晰的了解。本章末尾的图 7.19 和随附的模板方法讨论解释了如何用等效的模板方法替换 Fulfill MethodCongromoration 的实例。Factory MethodTemplate Method 的扩展,用于添加对象的创建和检索。

Factory Method is an interesting example in that it is a GoF pattern that explicitly uses another GoF pattern in its definition. In their discussion of Factory Method, GoF mentions that “Factory methods are usually called within Template methods” [21, p. 116]. Now we have a clear understanding of how they interact. Figure 7.19 at the end of this chapter and the accompanying discussion of Template Method explain how the instances of Fulfill Method and Conglomeration could be replaced with an equivalent Template Method. A Factory Method is an extension of a Template Method that adds the creation and retrieval of an object.

7.2. 结构模式

7.2. Structural Patterns

从 Structural 组中选择的两个模式是 DecoratorProxy。尽管它们的意图和明显的结构完全不同,但它们有一些令人惊讶的共同点。

The two patterns selected from the Structural group are Decorator and Proxy. Despite their quite different intents and apparent structures, they share some surprising commonalities.

7.2.1. 装饰器

7.2.1. Decorator

DecoratorChapter 4Section 4.2 中的驱动模式,在那里你看到了如何分析这个模式的详细讨论。现在我们可以在其配套设计模式中为其提供适当的上下文。图 7.7第 4.2 节中的图 4.17 封装在 PIN 盒中。

Decorator was the driving pattern in Chapter 4, Section 4.2, and there you saw a detailed discussion of how to analyze this pattern. Now we can give it a proper context within its companion design patterns. Figure 7.7 encapsulates Figure 4.17 from Section 4.2 in a PINbox.

 

 

图像

图 7.7.Decorator 包含在展开的 PIN 框中。

Figure 7.7. Decorator subsumed with the expanded PINbox.

通过将图 7.7 简化为图 4.18 中的 PIN 图,同样包装起来,我们可以生成一个简单的图表,如图 7.8 所示。现在,基本概念已明确说明,并且它们之间的联系是明确的。

By reducing Figure 7.7 to just the PIN diagram from Figure 4.18, similarly wrapped, we can produce a simple diagram, as in Figure 7.8. Now the underlying concepts are clearly stated, and their connections to each other are explicit.

图像

图 7.8.Decorator 仅作为 PIN。

Figure 7.8. Decorator as PIN only.

这个简单明了的图向我们展示了 Object RecursionExtend MethodDecorator 的内部组合。然而,为了本次讨论的目的,继续进行解构是有用的。我们可以以扁平的形式呈现图 4.22 中的信息,如图 7.9 所示。现在,我们已经剥离了 Object RecursionObjectifier PINbox,并直接公开了 Fulfill MethodTrusted Redirection 实例。

This simple and straightforward figure shows us the internal composition of Decorator from Object Recursion and Extend Method. For the purposes of this discussion, however, it is useful to continue the deconstruction further. We can present the information from Figure 4.22 in a flattened form, as in Figure 7.9. We’ve now peeled off the Object Recursion and Objectifier PINboxes and directly exposed the Fulfill Method and Trusted Redirection instances.

图像

图 7.9.Decorator 扩展了三层深并扁平化。

Figure 7.9. Decorator expanded three levels deep and flattened.

7.2.2. 代理

7.2.2. Proxy

代理限制对对象或对象结构的访问,对客户端隐藏详细信息。这是一个非常简单的模式,我们只需查看结构图即可执行分析,如图 7.10 所示。我们可以很容易地看到,有一个 Fulfill Method 的实例,对同一个方法的 from 的调用是 Deputized Redirection 的一个实例。Proxy::request()RealSubject

Proxy limits access to an object or object structure, hiding details from the clients. It is a simple enough pattern that we can perform our analysis by simply looking at the structure diagram, as shown in Figure 7.10. We can easily see that there is an instance of Fulfill Method, and the call from Proxy::request() to the same method in RealSubject is an instance of Deputized Redirection.

图像

图 7.10.代理归入展开的 PIN 码框。

Figure 7.10. Proxy subsumed with the expanded PINbox.

使用 Deputized Redirection 是此模式的重要特征。它表示我们将任务重定向到的对象是高度受信任的类型。为了清楚起见,我们可以将图 7.11 中的图表简化为仅 PIN,然后稍微重新组织图表,如图 7.12 所示。

The use of Deputized Redirection is the important characteristic of this pattern. It indicates that the objects to which we are redirecting a task are of highly trusted types. We can reduce this diagram to just the PIN for clarity, in Figure 7.11, and then reorganize the diagram a bit, as in Figure 7.12.

 

 

图像

图 7.11.仅代理为 PIN。

Figure 7.11. Proxy as PIN only.

图像

图 7.12.代理重新组织了 PIN 以更好地匹配 Decorator

Figure 7.12. Proxy PIN reorganized to better match Decorator.

洗牌的原因是,现在 Proxy 的定义更接近于我们在图 7.9 中从 Object Recursion 扩展的 Decorator 部分。如果我们仔细观察这两者,我们可以看到 Proxy 本质上是 Object Recursion 的一个高度可信的变体,但有一个显著的区别。当接收对象的实例被放置在其中一个子类中时,Object Recursion 的深度递归性质被破坏。相反,我们有一个单一的链接,即从 proxy 到 final 对象的一次性重定向。此外,我们可以看到 Proxy 缺少 Decorator 所具有的 Extend Method 实例。这表明,虽然 Decorator 将添加行为作为主要考虑因素,但 Proxy 没有。GoF 验证了这两点:“与 Decorator 不同,Proxy 模式不关心动态附加或分离属性,也不是为递归组合而设计的”[21,第 200 页]。这些差异现在清楚地显示在这两种模式的 PIN 图和组合中。

The reason for the shuffling is that now the definition for Proxy more closely matches the section of Decorator that we expanded out of Object Recursion in Figure 7.9. If we look at those two closely, we can see that Proxy is essentially a highly trusted variant of Object Recursion, with one significant difference. When the instance of the recipient object is placed within one of the subclasses, the deeply recursive nature of Object Recursion is disrupted. Instead, we have a single linkage, a one-time redirection from the proxy to the final object. Additionally, we can see that Proxy lacks the instance of Extend Method that Decorator has. This indicates that while Decorator has the addition of behavior as a prime consideration, Proxy does not. These two points are verified by GoF: “Unlike Decorator, the Proxy pattern is not concerned with attaching or detaching properties dynamically, and it’s not designed for recursive composition” [21, p. 200]. These differences are now clearly shown in the PIN diagrams and compositions of these two patterns.

7.3. 行为模式

7.3. Behavioral Patterns

GoF 的行为模式提供了一个有趣的挑战,因为本文中充实的四种基本依赖中只有一种是方法调用依赖,而我们只依赖于调用 EDP 的方法。然而,由我们的三个轴定义的设计空间,如 2.2.2 节,提供了足够的上下文信息,因此其中许多模式可以给出令人惊讶的稳健定义。特别是,Chain of ResponsibilityTemplate Method 值得研究。

The Behavioral patterns from GoF provide an interesting challenge given that the only one of the four kinds of basic reliances fleshed out in this text is the method-call reliance, and we’re relying solely on the method call EDPs. The design space defined by our three axes, as in Section 2.2.2, however, provides enough contextual information that many of these patterns can be given surprisingly robust definitions. In particular, Chain of Responsibility and Template Method are worth investigating.

7.3.1. 责任链

7.3.1. Chain of Responsibility

Chain of Responsibility 是一种与 ProxyDecorator 有很多共同点的模式。我们像往常一样,首先展示包装在 PIN 盒中的结构图,如图 7.13 所示。整体结构相当简单,仅由两个 EDP 组成。Extend Method 有多个实例,但是这个图很简单,我们可以放弃中间步骤,直接跳到仅 PIN 的图,如图 7.14 所示。

Chain of Responsibility is a pattern that has much in common with Proxy and Decorator. We start as usual by showing the structure diagram wrapped in a PINbox, as in Figure 7.13. The overall construct is fairly straightforward, being composed of only two EDPs. There are multiple instances of Extend Method, but this diagram is simple enough that we can forego the intermediate step and jump directly to the PIN-only diagram, as in Figure 7.14.

图像

图 7.13.责任包含在扩展的密码框中。

Figure 7.13. Chain of Responsibility subsumed within the expanded PINbox.

图像

图 7.14.责任仅作为 PIN。

Figure 7.14. Chain of Responsibility as PIN only.

事实上,如果你检查一下 EDP 衍生的针对 Decorator 的责任链定义,就会发现明显的区别是责任链重定向递归取代了 Decorator 的对象递归。如果使用 Chain of Responsibility 上的变体之一,使得 s 显式定义后继者 [21, p. 225],那么这里也可以找到 Object Recursion。因此,唯一的区别是 Object Recursion 在 pattern 中出现的位置。这是一个有趣的练习,在极小的概念变化中会导致截然不同的结果。Chain of ResponsibilityDecorator 在概念构成上非常接近,但最终却截然不同,以至于它们的创作者甚至没有将它们归为同一类别。ConcreteHandler

In fact, if you inspect the EDP-derived definition of Chain of Responsibility against Decorator, it becomes apparent that the salient difference is that the Redirected Recursion of Chain of Responsibility replaces the Object Recursion of Decorator. If one of the variants on Chain of Responsibility is used such that the ConcreteHandlers define the successor explicitly [21, p. 225], then Object Recursion will be found here as well. The only difference, then, is where in the pattern Object Recursion occurs. It is an interesting exercise in extremely small conceptual changes resulting in greatly different outcomes. Chain of Responsibility and Decorator are extremely close in their conceptual makeup, yet so different in the end that they were not even placed the same category by their creators.

7.3.2. 模板方法

7.3.2. Template Method

最后,Template Method 是另一个很好的例子,它以非常具体的方式将两个简单的概念组合在一起,以产生一个更强大的结构。

Finally, Template Method is another excellent example of combining two simple concepts in a very specific way to produce a much more powerful construct.

图 7.15 显示了封装在扩展 PIN 盒中的原始结构图。你可以看到 ConglomerationFulfill Method 的多个实例,为了清楚起见,让我们将其简化为一个简单的组合,如图 7.16 所示。请注意,Conglomeration 的 operation2 和 Fulfill Method 的 operation 指向相同的方法:的抽象。primitiveOperation1()AbstractClass

Figure 7.15 shows the original structure diagram encapsulated within an expanded PINbox. You can see multiple instances of both Conglomeration and Fulfill Method, so let’s reduce it down to a simple combination for clarity, as in Figure 7.16. Notice that operation2 of Conglomeration and operation of Fulfill Method point to the same method: the abstract primitiveOperation1() of AbstractClass.

图像

图 7.15.包含在展开的 PIN 框中的模板方法

Figure 7.15. Template Method subsumed within the expanded PINbox.

图像

图 7.16.模板方法简化为单个实例。

Figure 7.16. Template Method reduced to a single instance.

现在可以轻松提取生成的 PIN 图。图 7.17 清楚地表明,Conglomeration 实例正在调用一个抽象方法,该方法将由子类通过 Fulfill Method 处理。Conglomeration 的调用方法是 ,它调用的方法是同一类中的抽象方法。必然地,这意味着这个类是抽象的,并且是 Template MethodFulfill Method 的 Abstract 类,这确保了子类将提供适当的功能。因此,此子类履行 Fulfill Method 的 Concrete Class 角色和 Template Method 的相同角色。但是,要求 Conglomeration 的目标方法是 Fulfill Method 实例中的抽象方法,这会强制子类以某种方式处理细节,以便可以动态调整算法各部分的实现。templateMethodprimitiveOperation

Now the resultant PIN diagram can be extracted easily. Figure 7.17 clearly shows that the Conglomeration instance is calling into an abstract method that will be handled by a subclass through Fulfill Method. The calling method of Conglomeration is the templateMethod, and the method it is calling is the abstract primitiveOperation method in the same class. Necessarily, this means that this class is abstract, and the Abstract Class of both the Template Method and the Fulfill Method, which ensures that a subclass will provide the proper functionality. This subclass therefore fulfills the Concrete Class role of Fulfill Method and the same role of Template Method. Requiring the target method of a Conglomeration to be the abstract method in an instance of Fulfill Method, however, forces a subclass to handle the details in a way such that the implementation of portions of the algorithm can be adjusted dynamically.

图像

图 7.17.模板方法仅作为 PIN。

Figure 7.17. Template Method as PIN only.

图 7.18 显示了相同的 PIN 图信息,经过重新组织以再次显示与我们之前三个模式讨论的一致性。现在你可以看到,当 Proxy 使用 Fulfill MethodDeputized Redirection 的实例时,Template Method 将后一个实例替换为 Conglomeration 的一个实例。这消除了 Proxy 的受信任对象方面,而是在单个对象中提取功能。但是,值得注意的是,在 Proxy 模式中,主要关系是在 Proxy 元素中实现的抽象方法正在启动 Deputized Redirection 关系。相比之下,在 Template Method 中,主要特征是在 Concrete Class 元素中实现的抽象方法是 Conglomeration 实例的目标。从 Deputized RedirectionConglomeration 的转变,以及方法-调用关系的发起方和目标的交换都不是复杂或不寻常的,但它们共同导致了截然不同的结果。

Figure 7.18 shows the same PIN diagram information, reorganized to again show congruences with our previous three pattern discussions. Now you can see that where Proxy uses instances of Fulfill Method and Deputized Redirection, Template Method replaces the latter instance with one of Conglomeration. This eliminates the trusted objects aspect of Proxy and instead pulls the functionality within the single object. However, it is worth noting that in the Proxy pattern, the primary relationship is that the abstract methods fulfilled in the Proxy element are initiating the Deputized Redirection relationship. In contrast, in Template Method the primary feature is that the abstract methods fulfilled in the Concrete Class element are the targets of the Conglomeration instances. Neither the shift from Deputized Redirection to Conglomeration nor swapping of the initiator and target of the method-call relationship is complex or unusual, but together they lead to profoundly different results.

图像

图 7.18.模板方法重新组织了 PIN 以更好地匹配 Decorator

Figure 7.18. Template Method PIN reorganized to better match Decorator.

在对 Factory Method 的讨论中,您了解到 Template Method 是一个可能的实现功能,现在您可以看到它如何适应。图 7.19 显示了使用 Template Method 重新定义的 Factory Method,替换了之前 Fulfill MethodConglomeration 的单个实例。现在,这两种模式之间的关系是明确和明显的。Factory Method 使用模板方法来灵活地处理它嵌入了 RetrieveCreate Object 的组合使用的基础设施,就像在 Abstract Factory 中所做的那样。

In the discussion of Factory Method, you learned that Template Method was a possible implementation feature, and now you can see how this fits in. Figure 7.19 shows Factory Method redefined with Template Method replacing the previously individual instances of Fulfill Method and Conglomeration. Now the relationship between these two patterns is clearly defined and obvious. Factory Method uses a Template Method to flexibly handle the infrastructure within which it embeds a combined use of Retrieve and Create Object, much as is done in Abstract Factory.

图像

图 7.19.使用 Template Method 重新定义了 Factory Method

Figure 7.19. Factory Method redefined using Template Method.

7.4. 总结

7.4. Conclusion

在本章中,我们汇集了前几章中的概念和技术,综合了你在实践中会遇到的最常见设计模式的分析。讨论以有意义的方式将 EDP 和中间模式结合起来,以揭示程序设计高级概念的底层概念和结构。此外,您还学习了如何使用 PIN 以多种变体来说明这些模式,具体取决于您希望表达的细节级别或上下文。您了解了设计模式规范中的线索和提示如何指导您利用现有的其他模式知识来帮助您更好地了解新模式的作用。您还了解了设计模式如何相互使用和内部使用,例如隐藏在 Factory Method 中的 Template Method 实例。

In this chapter, we drew together the concepts and techniques from the prior chapters, synthesizing analyses of the most common design patterns you will encounter in your practice. The discussions combined the EDPs and intermediate patterns in meaningful ways to reveal the underlying concepts and structure of high-level concepts of program design. Furthermore, you learned how to illustrate these patterns with PIN in a number of variations depending on what level of detail or context you wish to express. You saw how clues and cues in design pattern specifications can point you to leveraging your existing knowledge of other patterns to help you form a better picture of what a new pattern does. You also gained an understanding of how design patterns, even at this higher level, can be used with and inside each other, such as the instance of Template Method buried inside Factory Method.

您对 EDP、PIN 的介绍到此结束,我希望这是一种新的、有趣的方式,让您了解我们行业中丰富的设计模式。您现在拥有了一套工具和技术,您可以使用这些工具和技术来进一步提高自己在软件设计方面的知识和技能,并通过帮助记录我们所拥有的内容来进一步加深我们对设计模式的集体理解。

This concludes your introduction to EDP, PIN, and what is, I hope, a new and interesting way for you to look at the rich tapestry of design patterns available in our industry. You’re now armed with a set of tools and techniques that you can use to further your own knowledge and skill in software design and to further our collective understanding of design patterns by helping document what we have.

欢迎来到社区。我期待看到我们接下来做什么。

Welcome to the community. I look forward to seeing what we do next.

A. ρ 微积分

A. ρ-Calculus

本附录旨在简要介绍元素设计模式、rho 演算或 ρ 演算背后的形式主义。对于那些有理论或研究倾向并且对更完整的论文感兴趣的人,以下出版物提供了对这些主题的正式处理。我希望您能找到一个合适的切入点,让您能够进入一个更丰富的编程可能性世界。您可能会发现在阅读此讨论时偶尔回顾第 2 章很有用。

This appendix is designed to be a brief introduction to the formalisms behind the Elemental Design Patterns, the rho-calculus, or ρ-calculus. For those of you with an inclination for theory or research and who are interested in a more complete treatise, the following publications provide formal treatments of the topics. I hope you find them a suitable entry point to a richer world of programming possibilities. You may find it useful to refer back to Chapter 2 occasionally while reading this discussion.

• 马丁·阿巴迪 (Martín Abadi) 和卢卡·卡德利 (Luca Cardelli)。对象理论。施普林格出版社,纽约,1996 年。

• Martín Abadi and Luca Cardelli. A Theory of Objects. Springer-Verlag, New York, 1996.

• 杰森·麦克·史密斯。SPQR:从源代码中自动检测设计模式的形式基础和实际支持。博士论文,北卡罗来纳大学教堂山分校,2005 年 12 月。

• Jason McC. Smith. SPQR: Formal Foundations and Practical Support for the Automated Detection of Design Patterns From Source Code. PhD thesis, University of North Carolina at Chapel Hill, Dec 2005.

Section 2.2.2 中,我们向您介绍了所有面向对象编程都可以只使用四样东西进行建模的想法:对象、方法、字段和类型。方法、字段和类型是相当明显的部分,因为它们分别表示编程的三个基本元素:功能、数据存储和数据描述。没有这三个元素,无论语言范式如何,任何计算都不会发生。我们为什么添加对象在当时有点被抛在一边。

In Section 2.2.2, you were introduced to the idea that all of object-oriented programming can be modeled using just four things: objects, methods, fields, and types. Methods, fields, and types are rather obvious pieces to include because they represent, respectively, the three basic elements of programming: functionality, data storage, and data description. No computation can occur without all three of those elements, regardless of language paradigm. Why we added objects was sort of waved aside at the time.

尽管它看起来仍然是自引用的,但添加对象的正式原因是它们构成了面向对象编程的核心和焦点。没有对象,就没有面向对象的编程。不满意?请看 Abadi 和 Cardelli 的 ς 演算 (sigma-calculus)。[1] ς-演算是构成 ρ-演算核心的基础表示语义。使用 ς 演算,我们可以将任何面向对象编程语言的语义1 简化为四个元素:对象、方法、字段和类型。2 编程元素的结果集足够小,我们可以提供它们组合和交互方式的完整枚举。换句话说,我们可以描述面向对象编程中的任何关系,而不能有其他关系。我们全面介绍了可能的面向对象编程关系。

Although it may still seem self-referential, the formal reason for adding objects is that they form the heart and focal point of object-oriented programming. Without objects, there is no object-oriented programming. Not satisfying? Look to Abadi and Cardelli’s ς-calculus (sigma-calculus) instead. [1] ς-calculus is the underlying denotational semantics that forms the core of ρ-calculus. Using ς-calculus, we can reduce any object-oriented programming language’s semantics1 to the four elements: objects, methods, fields, and types.2 The resulting set of the elements of programming is sufficiently small that we can provide a complete enumeration of the ways in which they can combine and interact. In other words, we can describe any relationship in object-oriented programming, and there can be no others. We have a complete coverage of the possible object-oriented programming relationships.

从另一个角度来看,面向对象编程已经抵制了通过使用传统过程编程的方法(作为函数)、字段和类型来可靠和充分地建模。在尝试这样做的过程中,会丢失一些基本的东西。学者们已经就这一特定问题争论了三十年,但据我所知,每一次尝试都将 lambda 演算的语义扩展为过程编程的基础,以包含面向对象的技术,在某种程度上都失败了。在每种情况下,无论过程扩展与面向对象系统的语义建模多么紧密,需要接近的数学技巧都是极其繁琐、迟钝的,并且无法与使用面向对象编程语言的概念优雅表现出任何一致性。另一方面,ς 演算通过将对象本身添加为一等实体,成功地创建了一个数学模型,该模型反映了面向对象编程的心智模型,并为以正式方式处理对象提供了一种可靠而优雅的方法。

From another perspective, object-oriented programming has resisted being reliably and sufficiently modeled by using just the methods (as functions), fields, and types of traditional procedural programming. Something fundamental is lost in attempting to do so. Scholars have argued about this particular point for three decades now, but every attempt that I know of to extend the semantics of the lambda-calculus underlying procedural programming to encompass object-oriented techniques has fallen short in some way. In every case, regardless of how closely the procedural extensions model the semantics of object-oriented systems, the mathematical machinations required to come close are extremely cumbersome, obtuse, and fail to show any coherence with the conceptual elegance of using object-oriented programming languages. ς-calculus, on the other hand, by adding objects as first-class entities in their own right, successfully created a mathematical model that mirrors the mental model of object-oriented programming and gives a solid and elegant approach for working with objects in a formal manner.

A.1. Reliance 运算符

A.1. Reliance Operators

ς 演算还扫除了四个必需和必要的实体(对象、方法、字段和类型)之外的所有内容,为真正全面的简单分析开辟了道路。例如,如果只有 4 个项目可供使用,则只有 16 种方法可以组合它们。表 2.2 说明了这一点,表 A.1 中再现了这一点。

ς-calculus also swept aside everything but the four required and necessary entities—objects, method, fields, and types—opening the way for simple analyses that are truly comprehensive. For example, given only four items to work with, there are only sixteen ways they can be combined. This point was illustrated in Table 2.2, which is reproduced here in Table A.1.

表 A.1.面向对象编程的实体之间的所有交互

Table A.1. All interactions between entities of object-oriented programming

图像

这张桌子足够小,我们可以一块一块地把它拆开。我们可以通过研究 Dedefines 一次性解决此表的大部分内容。对象定义其内部的方法、字段或类型。名称空间或包对象等对象也可以定义其中的其他对象。在某些语言中,方法可以定义内部方法和其中的类型,但几乎总是可以定义对象和字段。字段本质上不容易包装或定义其他元素,但字段的类型(例如类)可以定义这四个元素中的任何一个。

This table is small enough that we can pick it apart piece by piece. We can address most of this table at one shot by investigating Defines. An object defines a method inside of it, or a field, or perhaps a type. Objects such as namespaces or package objects can also define other objects within them. Methods can, in some languages, define inner methods and types within them but can almost always define objects and fields. Fields, by their nature, do not readily wrap or define other elements, but the type of the field, such as a class, can define any of the four elements.

这种内部实体的定义也称为范围界定。定义方法的类在其内部限定该方法的范围。您必须遍历类才能访问该方法,可以直接或通过从该类实例化的对象或字段。对象限定其子对象的范围,方法也是如此。在 C++ 中,这些示例包括分别确定类中的静态方法或命名空间中的字段的范围: 和。Java 避免使用双冒号范围运算符,而是全面统一范围表示法以使用简单的点,如 or 中所示。这与 ς-calculus 使用的符号相同。在所有情况下,范围都由点运算符指示。对于大多数程序员来说,它看起来应该非常熟悉,因为它既用于限定绑定实例的范围,也可用于从定义中选择实体。在实践中真的没有区别。事实上,defines 只是 scope 的同义词。Some-Class::aMethod()ANamespace::aFieldSomeClass.aMethod()APackage.aField

This defining of inner entities is also known as scoping. A class that defines a method is scoping that method inside itself. You must go through the class to get to the method, either directly or through an object or field instantiated from that class. Objects scope their children, as do methods. Examples of these in C++ include scoping a static method in a class or a field in a namespace: Some-Class::aMethod() and ANamespace::aField respectively. Java eschews the double colon scoping operator and unifies the scoping notation across the board to use a simple dot, as in SomeClass.aMethod() or APackage.aField. This is the same notation that ς-calculus uses. In all cases, scoping is indicated by the dot operator. It should look very familiar to most programmers because it is used both to scope through bound instances and select entities out of a definition. There’s really no difference in practice. In fact, defines is just a synonym for scopes.

那么,为什么我不直接使用 Scopes 呢?过程编程的传统表示演算、λ 演算和 ς 演算之间的关键区别在于,从字段中选择一个元素会起 σ 运算符的作用。这就是将实例绑定到类型的原因,允许从字段提供的作用域中正确查找所选项目,即使存在多态性也是如此。这种绑定是 ς 演算所独有的,并且与将对象视为一等实体相结合,使其非常有效和优雅。但是,出于本文的目的,我们可以忽略此约束。

So why didn’t I just use Scopes instead? The critical difference between the traditional denotational calculus of procedural programming, λ-calculus, and ς-calculus is that selecting an element from a field brings into play the σ operator. This is what binds an instance to a type, allowing for proper lookup of the selected item out of the scoping provided by the field, even when polymorphism is present. This binding is unique to ς-calculus and, combined with the treatment of objects as first-class entities, is what makes it extremely effective and elegant. We can, however, ignore this binding for the purposes of this text.

消除定义作为一个已知概念,我们只剩下表 2.3,这里再次重现为表 A.2

Eliminating defines as a known concept leaves us with Table 2.3, again reproduced here, as Table A.2.

表 A.2.面向对象编程的实体之间的非作用域交互

Table A.2. Nonscoping interactions between entities of object-oriented programming

图像

对象和字段被声明为 “属于一个类型”。ς-calculus 将其显示为 anObjectitsType。此表示法还用于指示方法的返回类型。类型是子类型或彼此继承的类型。这在 ς 演算中显示为 Subtype <: Supertype3 这消除了表 A.2 的全部内容,除了四个依赖运算符:方法调用、字段使用、状态更改和内聚。

Objects and fields are declared to be “of a type.” ς-calculus displays this as anObject: itsType. This notation is also used to indicate the return type of a method. Types subtype or inherit from one another. This is shown in ς-calculus as Subtype <: Supertype.3 That eliminates the entirety of Table A.2 except for the four reliance operators: method call, field use, state change, and cohesion.

在 ρ 演算中,有四个依赖运算符(简称 relops),基于上述四种关系。方法-方法 relop(即方法调用)是 mu-form。这是本书中讨论的单一依赖运算符。method-field (字段 use) 是 phi 形式。field-method 是一种状态更改,换句话说,field 的状态以某种方式依赖于方法调用,称为 sigma-form。最后,场-场依赖是传统的内聚或 kappa 形式。这些在 ρ 微积分的数学中分别表示为 <μ<σ<κ。该表示法来自子类型运算符 <:,您可以将其视为“子类型的存在和意图依赖于超类型”。因为 : 是一个类型运算符,所以我们可以将 < 重新解释为 “depends on”。然后,此基本表示法与适当的指示符下标,以形成四个 reliance 运算符。它们是二元运算符,从左侧形成对右侧使用的依赖。换句话说,someMethod anotherMethod 应该被理解为 someMethod 依赖于 anotherMethod

There are four reliance operators—relops for short—in ρ-calculus, based on the four relationships described above. The method-method relop (that is, a method call) is the mu-form. This is the single reliance operator discussed in this book. The method-field—a field use—is the phi-form. The field-method is a state change—in other words, the state of the field depends on a method call in some manner—known as the sigma-form. Finally, a field-field reliance is the traditional cohesion, or kappa-form. These are denoted in the mathematics of ρ-calculus as <μ, <φ, <σ, and <κ, respectively. The notation comes from the subtyping operator <:, which you can think of as stating, “The subtype relies on the supertype for its existence and intent.” Because : is a typing operator, we can reinterpret < as “relies on.” This basic notation is then subscripted with the appropriate indicator to form the four reliance operators. They are binary operators that form a reliance from the left-hand side of their use on their right-hand side. In other words, someMethod <μ anotherMethod should be read as someMethod relies on anotherMethod.

A.2. 传递性和同位素

A.2. Transitivity and Isotopes

Section 4.1.1 中,您了解了同位素以及它们是如何从间接依赖形成的。我们可以使用 ρ 演算通过传递性为这个过程提供正式的基础。以下源示例重现了 Section 4.1.1 中的代码示例。我们可以像以前一样声明这依赖于 ,但现在我们可以将其写为 f .foo b.bar。目前为止,一切都好。在右侧的示例中,我们可以说 f .foo <μ g.goo,g.goo b.barf.foob.bar

In Section 4.1.1, you learned about isotopes and how they are formed from indirect reliances. We can use ρ-calculus to give this process a formal basis through transitivity. The following source example reproduces our code example from Section 4.1.1. We can state, as before, that f.foo relies on b.bar, but now we can write this as f .foo <μ b.bar. So far, so good. In our example on the right side, we can say that f .foo <μ g.goo and that g.goo <μ b.bar.

图像

我们可以在 reduction 规则中使用这两个信息。这是 ς 演算和 ρ 演算中的主要推理形式。给定一组我们知道为 true 的子句,我们可以推断出一个新属性。我们说我们正在将 clauses 简化为 result。例如,在这种情况下,传递性有一个约简规则,如方程 A.1 所示。

We can use these two bits of information in a reduction rule. This is the primary form of inference in ς-calculus and ρ-calculus. Given a set of clauses that we know to be true, we can infer a new property. We say that we are reducing the clauses to the result. For instance, in this case, there is a reduction rule for <μ transitivity, as in Equation A.1.

图像

此缩减规则首先声明我们有一个环境 E,以便我们在此环境中定义了 xyz。接下来的三个子句指出,这些元素被定义为分别具有子元素 mno,这些子元素被指示为方法。接下来的两个子句指定 relops,以便 x.m 依赖于 y.n而 y.n 依赖于 z.o。给定这组子句,推断的结果是 x.m 依赖于 z.o。这只是 ρ 演算中众多传递性之一,它们赋予了 ρ 演算很大的解析强度。超大型系统可以简化为简单的 relop 集,并且可以通过查看结果的简化视图来搜索高级抽象(如设计模式)的存在。如果没有这种技术,我们将不得不不断地处理或检查所有中间的作品。例如,如果我们正在寻找 x.m 调用 z.o 的证据,那么对这个系统进行简单的检查将无法找到它。通过使用 ρ 演算的传递性并说明我们正在寻找 x.m 依赖于 z.o 的情况,我们可以快速而清晰地证明这种关系的存在。

This reduction rule begins by stating that we have an environment E such that we have defined in this environment x, y, and z. The next three clauses state that these elements are defined as having subelements m, n, and o, respectively, which are indicated to be methods. The next two clauses specify relops such that x.m relies on y.n, and y.n relies on z.o. Given this set of clauses, the inferred result is that x.m relies on z.o. This is just one of the many transitivities in ρ-calculus, and they are what give ρ-calculus much of its analytic strength. Extremely large systems can be reduced to simple sets of relops, and the existence of high-level abstractions such as design patterns can be searched for by looking in the simplified views of the results. Without this technique, we would have to continually work with or examine all of the intervening pieces. If, for example, we were looking for proof that x.m called z.o, a simple examination of this system would fail to find it. By using the transitivities of ρ-calculus and stating that we are looking for a situation where x.m relies on z.o instead, we can prove the existence of the relationship quickly and cleanly.

更准确地说,这种传递性允许我们为设计模式创建特定的基于依赖的定义,这些定义可以匹配大量的结构实现。这些是规范、最基本和最直接实现的同位素。这就是这种方法如此强大和灵活的原因。

More precisely, this transitivity allows us to create specific reliance-based definitions for design patterns that can match a tremendous number of structural implementations. These are the isotopes of the canonical, most basic and direct implementation. This is what makes this approach so powerful and flexible.

A.3. 相似性

A.3. Similarity

我们在 2.2.2 节中的三个相似轴同样可以在 ρ 微积分中给出正式的基础。快速提醒一下,单个 relop 的两面之间有三种可能的相似之处。例如,给定 f.foo <μ b.bar 的依赖性,我们可以讨论 fb 之间的对象相似性,fb 的类型之间的相似性,以及方法 foobar 之间的相似性。我们使用 ~ 表示 ρ 演算中的相似性,使用 表示不相似性。例如,A ~ B 表示“AB 相似”,B C 表示“BC 不同”。

Our three axes of similarity from Section 2.2.2 can likewise be given a formal basis in ρ-calculus. As a quick reminder, there are three possible ways in which a single relop can have similarities between its two sides. For instance, given the reliance f.foo <μ b.bar, we can talk about the object similarity between f and b, the similarity between the types of f and b, and the similarity between the methods foo and bar. We use ~ to indicate similarity in ρ-calculus and for dissimilarity. For instance, A ~ B for “A is similar to B” or B C for “B is dissimilar to C.”

类型之间的相似性可以在 fb 的 ρ 演算定义中找到。为了让我们在 ρ 演算定义中使用 fb,必须定义它们,并且必须为它们指定类型。因此,必须已经定义了 fb 的类型信息。然而,我们可以将这种类型关系讨论为以下之一:unrelated,我们只称之为 dissimilar,用 表示;完全相同的类型,我们称之为 similar,标记为 ~;子类型化,由前面看到的 <:表示;以及同级类型,其中两种类型共享一个公共超类型,由 <> 表示。所有这些信息都已经存在于 ρ 演算中。

The similarity between the types is found in the ρ-calculus definition of f and b. For us to use f and b in a ρ-calculus definition, they must be defined, and they must be given types. Therefore, the typing information of f and b must already be defined. We can talk about this typing relationship, however, as one of the following: unrelated, which we will just call dissimilar, and denote by ; the same exact type, which we call similar and mark as ~; subtyping, denoted by the previously seen <:; and sibling typing, in which two types share a common supertype, indicated by <:>. All of this information is already present in ρ-calculus.

我们也可以使用相同的 ~/ 表示法来讨论对象和方法之间的相似性,即相似或不同。但是,为了简洁起见,并且由于此信息对于特定的 reliance 运算符实例是唯一的,我们可以将此信息作为上标直接添加到 reliance 运算符表示法中,尽管形式略有修改。

We can talk about the similarity between objects and methods as either similar or dissimilar as well, using the same ~/ notation. For brevity, however, and because this information is unique to specific reliance operator instances, we can add this information to the reliance operator notation directly as a superscript, albeit in a slightly modified form.

尽管 ~/ 表示法在正常文本大小下很容易区分,但在小字体下会出现可读性问题,因此为了清晰起见,使用 +/– 代替。4 点运算符的选择表示法在相似性表示法中被模拟:对象相似性放在点的左侧,方法相似性放在右侧。例如,具有对象不相似性和方法相似性的方法调用 relop 将表示为 图像。具有对象相似性和方法不相似性的 relop 将显示为 图像,依此类推。这让我们可以将大量信息折叠成一个简洁的形式。结合传递性,它使我们能够快速、精确、清晰地描述系统中任意两个实体之间的关系。

Although the ~/ notation is easily distinguishable at normal text sizes, at small font sizes it presents readability problems, so +/– is used instead for clarity.4 The selection notation of the dot operator is mimicked in the similarity notation: the object similarity is placed on the left of a dot and the method similarity is placed on the right. For instance, a method call relop with object dissimilarity and method similarity would be noted as . A relop with object similarity and method dissimilarity would be shown as , and so on. This lets us collapse a tremendous amount of information into a concise form. Combined with the transitivities, it lets us describe relationships between any two entities in a system quickly, precisely, and cleanly.

您可能会想,我们可能并不总是知道系统的两个元素之间的相似之处或不同之处。你是对的。在这种情况下,圆 ο 用于传达真正未知的关系。

It might occur to you that we may not always know the similarity or dissimilarity between two elements of a system. You are correct. In such cases, a circle, ο, is used to convey a truly unknown relationship.

A.4. EDP 形式主义

A.4. EDP Formalisms

我们现在有了正式定义和使用 ρ 演算为 EDP 创建基础的基础。我们可以重新访问第 4.4 节中的 EDP 设计空间图,并使用我们的新符号重新注释它们,如图 A.1A.2 所示。

We now have the basis for formally defining and working with ρ-calculus to create the foundation for the EDPs. We can revisit the EDP design space diagrams from Section 4.4 and reannotate them with our new notation, as in Figures A.1 and A.2.

图像

图 A.1.完整的方法调用 EDP 设计空间:类似的方法。

Figure A.1. The full method call EDP design space: similar method.

图像

图 A.2.完整的方法称为 EDP design space: disdifferent method。

Figure A.2. The full method call EDP design space: dissimilar method.

让我们为这个讨论建立一个简单的设置。假设我们有一个 A 类型的对象 o 和一个 B 类型的对象 p。我们可以将它们分别写成 oApB。类型 A 定义方法 f,类型 B 定义方法 g。这种设置可以用代码来显示,如清单 A.1 所示。

Let’s establish a simple setup for this discussion. Assume we have an object o of type A and an object p of type B. We can write these as o : A and p : B, respectively. Type A defines a method f and type B defines a method g. This setup could be shown in code something like in Listing A.1.

现在,我们可以使用相似性符号和依赖运算符,根据 EDP 在设计空间中的位置来定义 EDP。

We can now define the EDPs on the basis of their placement within the design space, using our similarity notation and reliance operators.

查看图 A.1,我们可以看到 Recursion 位于对象、类型和方法相似性轴的交点处。这意味着递归可以正式表示为 o.f 图像 p.gA ~ B。回想一下,上标表示匹配范围界定点运算符两侧的两个元素之间的关系。在这种情况下,o 类似于 p,f 类似于 g

Looking at Figure A.1, we can see that Recursion is at the intersection of the axes of object, type, and method similarity. This means that Recursion can be expressed formally as o.f p.g, A ~ B. Recall that the superscript indicates what the relationship is between the two elements on either side of the matching scoping dot operator. In this case, that o is similar to p and f is similar to g.

你可以看到 Delegation 可以写成 o.f 图像 p.gA B,而且,正如你所料,Redirection 写成 o.f 图像 p.g因此,第 2.2.4 节中关于重定向委托共同构成耦合概念的声明可以表述为 o.f 图像 p.g.。我们的 “unknown” 符号终于开始发挥作用,尽管方式很小。

You can see that Delegation can be written as o.f p.g, A B, and, as you might expect, Redirection is written o.f p.g. The claim from Section 2.2.4 that Redirection and Delegation together form the concept of coupling can be stated, therefore, as o.f p.g. Our “unknown” notation finally comes into play, albeit in a minor way.

清单 A.1.简单代码示例

Listing A.1. Simple code example


   A {

2 无效 f();

};



4 B 类 {

6 void g();

};


8
个 A o;

10 B 页;

   class A {

 2     void f();

   };

 4

   class B {

 6     void g();

   };

 8

   A o;

10 B p;


我很确定你能猜到此时的 Conglomeration 是什么样子的:o.f 图像 p.g,现在 2.2.4 节中讨论的内聚力简化为 o.f 图像 p.g,因为来自 Recursion 的相似性和来自 Conglomeration 的不相似性相互抵消。

I’m pretty sure you can guess what Conglomeration looks like at this point: o.f p.g, and now the cohesion discussed in Section 2.2.4 reduces to o.f p.g, as the similarity from Recursion and the dissimilarity from Conglomeration cancel each other out.

对于 Redirected Recursion,方法 reliance 是您对 Redirection 的期望:o.f 图像 p.g.顺便说一句,这是我们使用 Redirected Recursion 作为名称的正式理由 — 这是 Redirection 的改进,而不是 Recursion。因此,只需将类型关系更改为 A = B

For Redirected Recursion, the method reliance is what you’d expect for a Redirection: o.f p.g. By the way, this is our formal reasoning for going with Redirected Recursion as a name—this is a refinement of Redirection, not Recursion. As a result, only the type relationship needs to be changed, to A = B.

对于图 A.1 底行的最后两个 EDP,我们添加了子类型和同级关系。同样,方法依赖是你对 Redirection 的期望,o.f 图像 p.g,再一次,我们只更改了类型关系。这次它设置为 A <B,这表示 AB 的子类型,从而产生受信任的重定向 EDP。

For the final two EDPs along the bottom row of Figure A.1, we add our subtyping and sibling relationships. Again, the method reliance is what you’d expect for a Redirection, o.f p.g, and once more we only change the type relation. This time it is set to A <: B, which indicates that A is a subtype of B, giving rise to the Trusted Redirection EDP.

我们可以通过再次从 Redirection 开始来获得 Deputized Redirection,这次对类型依赖关系进行简单的更改,以便 A <:> B

We can arrive at Deputized Redirection by again starting with Redirection, this time making a simple change to the type reliance relationship, such that A <:> B.

最后,让我们重新审视 Recursion。从它的调用依赖运算符和类型语句开始: o.f 图像 p.goApB. 将类型关系更改为 A <B. 这就是修改 Recursion 以将其演变为 Extend Method 的全部内容。

Finally, let’s revisit Recursion. Start with its call reliance operator and type statements: o.f p.g, o : A, p : B. Change the type relationship to A <: B. That’s all there is to modifying Recursion to evolve it into Extend Method.

我们可以使用这种方法来定义这个设计空间中的任何 EDP,使用 ρ 演算优雅而简洁地表达它们的核心关系。第 A.7 节使用第 A.2 节中的缩减规则表示法为每个 EDP 提供了完整的定义。给定适当的子句集,我们可以推断 reduction 规则底部的事实是显而易见的,并反过来将其用作其他 reduction 规则推理中的新子句。例如,以 Recursion 的定义为例:

We can use this approach to define any of the EDPs within this design space, elegantly and concisely expressing their core relationships using the ρ-calculus. Section A.7 provides full definitions for each EDP using our reduction rule notation from Section A.2. Given the proper set of clauses, we can infer that the fact at the bottom of a reduction rule is evident and in turn use it as a new clause in other reduction rule inferences. For instance, take the definition for Recursion:

图像

该行上方的子句类似于我们在定义 Recursion 时看到的。第一个子句指出 Recursor 是一种类型,并且它至少有一个名为 operation 的方法,以及其他可选方法和/或字段。第二个子句确定对象 r 的类型为 Recursor。第三个子句指出 r.operation 有一个依赖于自身的方法调用,对象和方法都相似。最后一行指出,前面的子句中的元素构成了 Recursion实例。我们使用正式定义中的名称作为模式的角色名称。这些既可用于讨论模式,也可用于定义其等效 PIN 表示的结构,如 A.6 节所示。绑定到这些角色的槽是数学意义上的变量。一旦实体绑定到角色,该角色就被称为由该实体履行。在上述情况下,任何满足定义中所述的递归需求的类都被称为绑定到递归的 Recursor 角色。

The clauses above the line are analogous to what we saw when we defined Recursion. The first clause states that Recursor is a type and that it has at least one method, which is named operation, with other optional methods and/or fields. The second clause establishes that the object r is of type Recursor. The third clause states that r.operation has a method call reliance on itself, with both object and method similarity. The final line states that the elements in the prior clauses form an instance of Recursion. We use the names in the formal definition as the names of the roles for the pattern. These are used both when discussing the pattern and for defining the structure of its equivalent PIN representation, as you’ll see in Section A.6. The slots that are bound to these roles are variables in the mathematical sense. Once an entity is bound to a role, that role is said to be fulfilled by that entity. In the preceding case, any class that satisfies the needs of Recursion as stated in the definition is said to be bound to the Recursor role of Recursion.

A.5. 合成和缩减规则

A.5. Composition and Reduction Rules

从这些定义开始,我们可以将它们组合成更复杂的模式,就像我们在前面的章节中所做的那样。让我们重新检查一下我们在 4.2 节 中进行的 Decorator 的构建。概括地说,我们引入了 Fulfill Method 作为 InheritanceAbstract Method 的组合,然后使用 Fulfill Method 来定义 Objectifier。我们将 ObjectifierTrusted Redirection 组合在一起来定义对象递归,然后将其与 Extend Method 结合在一起以创建 Decorator

Starting with these definitions, we can compose them into more complex patterns, just like we did in the earlier chapters. Let’s reexamine the building of Decorator that we undertook in Section 4.2. To recap, we introduced Fulfill Method as a combination of Inheritance and Abstract Method, and then used Fulfill Method to define Objectifier. We combined Objectifier and Trusted Redirection to define Object Recursion, which was then brought together with Extend Method to create Decorator.

让我们首先将 Fulfill Method 定义为:

Let’s start by defining Fulfill Method as:

图像

第一个子句声明 Abstract Interface 的实例,它采用两个参数,第一个是类型实体,第二个是方法实体。它按照 Abstract Interface 模式中的定义建立了它们之间的关系,但允许我们用这个较短的版本替换完整的正式定义。第二个子句通过 Inheritance EDP 的实例确定 ConcreteClassAbstractor 的子类。第三行是拼图的新部分。它指出 Abstract Interface 中陈述的方法由 ConcreteClass 定义,实现了 Abstract Interface 的承诺。请注意,这部分至关重要,因为不需要子类来实际定义抽象方法——它可能只是类层次结构中的另一个抽象类。子句中提供的名称并非偶然。如果名称出现在缩减规则中的多个位置,则每个角色中都使用系统中的同一实体。这就是将较小的模式联系在一起形成一个更大的构图的原因。

The first clause declares an instance of Abstract Interface, which takes two arguments, the first a type entity, and the second a method entity. It establishes the relationship between them as defined in the Abstract Interface pattern but allows us to replace the full formal definition with this shorter version. The second clause establishes that ConcreteClass is a subclass of Abstractor through an instance of the Inheritance EDP. The third line is the new piece of the puzzle. It states that the method stated in Abstract Interface is defined by ConcreteClass, fulfilling the promise made by Abstract Interface. Note that this piece is crucial, as there is no need for a subclass to actually define the abstract method—it may just be another abstract class in the class hierarchy. The names provided in the clauses are not accidental. If a name appears in more than one position in a reduction rule, then the same entity in the system is being used in each role. This is what ties together the smaller patterns into a larger composition.

我们可以继续减少规则过程,并通过将 Client 类添加到多个要抽象的操作中来定义 Objectifier

We can continue the reduction rules process and define Objectifier by adding a Client class to a plurality of operations to abstract:

图像

可信重定向 (Trusted Redirection) 看起来更像我们之前对递归的定义:

Trusted Redirection looks rather like our earlier definition for Recursion:

图像

Object Recursion 的正式定义是:

The formal definition of Object Recursion is then:

图像

我们正在确定一些 Objectifier ConcreteClasses 参与 Trusted Redirection,而另一些则不涉及。如果重定向涉及所有这些类,则对此体系结构的调用将永远不会终止。

We’re establishing that some Objectifier ConcreteClasses are involved in Trusted Redirection and some are not. If all such classes were involved in redirection, a call into this architecture would never terminate.

接下来,我们可以使用速记表示法来确定元素是类的方法,如 aMethod methaClass) 中所示,并将 Extend Method 定义为:

Next, we can use a shorthand notation for establishing that an element is a method of a class as in aMethod meth(aClass) and define Extend Method as:

图像

最后,我们可以将 Decorator 定义为:

Finally, we can define Decorator as:

图像

请注意,这些方程可以与 Section 4.2 中使用的 PIN 图密切相关,以说明组成。事实上,正如我们接下来将看到的,PIN 和缩减规则中模式实例的使用是相辅相成的。

Note that these equations can be correlated closely with the PIN diagrams used in Section 4.2 to illustrate the compositions. In fact, as we’ll see next, PIN and the uses of pattern instances in reduction rules go hand in hand.

A.6. 模式实例表示法和角色

A.6. Pattern Instance Notation and Roles

PIN 的一个设计目标是允许通过扩展的 PINbox 简单地显示组合的模式。PIN 的规则与本附录中的缩减规则组合的规则非常相似。例如,图 A.3图 3.9重复,这些模式的实例在 ρ 演算的形式中将显示为 PatternA(Role1 : a, Role2 : b) 和 PatternB(Role1 : a, Role2 : b, Role3 : c, Role4 : d, Role5 : e, Role6 : f),其中 af 表示未绑定的角色。

One design goal of PIN is to allow the simple display of composed patterns through expanded PINboxes. The rules for PIN closely mirror those for the reduction rule compositions in this appendix. For instance, Figure A.3 is a repeat of Figure 3.9, and instances of those patterns in the formalism of ρ-calculus would appear as PatternA(Role1 : a, Role2 : b) and PatternB(Role1 : a, Role2 : b, Role3 : c, Role4 : d, Role5 : e, Role6 : f), where a through f represent unbound roles.

图像

图 A.3.标准 PIN 码框。

Figure A.3. Standard PINbox.

同样,图 A.4 显示了图 3.14 中扩展的 PINbox 示例,我们现在可以将其正式定义为:

Likewise, Figure A.4 shows our expanded PINbox example from Figure 3.14, which we can now define formally as:

图像
图像

图 A.4.扩展的 PIN 实例。

Figure A.4. Expanded PIN instance.

PINbox 中的角色与正式定义中的角色之间存在一对一的关联。此外,PINbox 图中的每个连接都说明了在定义中的多个角色实现中使用相同的绑定变量的点。

There is a one-to-one correlation between the roles ringing a PINbox and the roles in the formal definition. Also, each connection in the PINbox diagram illustrates a point at which the same bound variable is used in multiple role fulfillments within the definition.

A.7. EDP 定义

A.7. EDP Definitions

本节使用 ρ 演算为本文中的每个 EDP 提供了完全正式的定义。尽管您已经看到了所使用的大多数表示法,但并非这些定义的每个方面都得到了完整的解释。如果您想要更深入的了解,请参阅基础出版物以获取完整参考,特别是 A Theory of Objects [1],它提供了您需要的全面基础,以及 SPQR:从源代码中自动检测设计模式的形式基础和实际支持 [35],其中提供了原始定义和对每个定义的更深入讨论。自最初发布以来,一些细微的细节发生了变化,例如在模式实例中添加了角色的显式命名。在原始参考文献发表后,发现这非常有用。以下每个定义都附有其等效的 PIN 图,因此您可以立即看到与形式主义的相关性。

This section provides fully formal definitions of each of the EDPs in this text, using ρ-calculus. Not every aspect of these definitions is explained in full, although you have seen most of the notation used. If you would like a deeper treatment, refer to the base publications for the complete reference, in particular A Theory of Objects [1], which provides the comprehensive foundation you need, as well as SPQR: Formal Foundations and Practical Support for the Automated Detection of Design Patterns from Source Code [35], which provides the original definitions and a much deeper discussion of each. Some minor details have changed since original publication, such as the addition of the explicit naming of the roles in pattern instances. This was found to be of great utility after the original references were published. Each of the following definitions is accompanied by its equivalent PIN diagram so you can immediately see the correlation with the formalism.

A.7.1. 创建对象

A.7.1. Create Object

这个定义在 ς 微积分中最接近其基础,如果你想真正详细理解这个定义,建议研究这个基础。或者,你可以接受这是一个非常简单的概念的非常具体的定义。

This definition is the closest to its foundations in ς-calculus, and studying that foundation is recommended if you wish to truly understand this definition in detail. Or, you can accept that this is a very specific definition for a very simple concept.

图像

其中图像(可能是),A = A(对于 O-1),X <A (O-2)和 X < #A(对于 O-3 兼容语言),根据 [1] 中建立的分类。

where (and may be Root), A = A for O-1, X <: A for O-2, and X < #A for O-3-compliant languages, respectively, according to the classifications established in [1].

图像

请注意,将其视为直接从语言模型的公理输出可能比尝试从 ς 演光结构中推导出它可能更容易。特别是,如果尝试执行源代码分析,请使用语言语义,而不是尝试从 ς 演算基元中推导出此语义。

Note that it may very well be easier to consider this as an axiomatic output directly from a language model than to try to derive it from ς-calculus constructs. In particular, if attempting to perform analysis of source code, use the language semantics instead of trying to derive this from ς-calculus primitives.

A.7.2. 检索

A.7.2. Retrieve

大多数面向对象语言不允许使用新值(方法主体)更新方法。相反,只能以这种方式更新数据字段。从理论的角度来看,这两种情况之间没有明显的区别。我们同时允许两者,依靠语言语义来控制哪些情况有效,哪些情况无效。通过将我们的定义推迟到我们有一组格式良好的 ρ 演算事实,我们避免了处理无数语言怪癖的大部分复杂性。Retrieve 模式有两种基本形式:

Most object-oriented languages do not allow the updating of methods with new values (method bodies). Instead, only data fields can be updated in this manner. From a theoretical point of view, there is no appreciable difference between these two scenarios. We allow for both, relying on language semantics to govern which cases are valid and which are not. By deferring our definition until we have a set of well-formed ρ-calculus facts, we avoid much of the complexity of handling the myriad of language quirks. There are two basic forms for the Retrieve pattern:

图像
图像

这两种形式都由第一个子句中的更新和第二个子句中的值返回组成,要么是 name (表示共享引用),要么是 by value (表示新副本)。

Both forms are composed of an update in the first clause and a return of a value in the second clause, either by name, indicating a shared reference, or by value, indicating a fresh copy.

图像

A.7.3. 继承

A.7.3. Inheritance

图像
图像

A.7.4. 抽象接口

A.7.4. Abstract Interface

图像
图像

A.7.5. 委托

A.7.5. Delegation

图像
图像

A.7.6. 重定向

A.7.6. Redirection

图像
图像

A.7.7. 集合

A.7.7. Conglomeration

图像
图像

A.7.8. 递归

A.7.8. Recursion

图像
图像

A.7.9. Revert 方法

A.7.9. Revert Method

图像
图像

A.7.10. Extend 方法

A.7.10. Extend Method

图像
图像

A.7.11. 委托企业集团

A.7.11. Delegated Conglomeration

图像
图像

A.7.12. 重定向递归

A.7.12. Redirected Recursion

图像
图像

A.7.13. 可信委托

A.7.13. Trusted Delegation

图像
图像

A.7.14. 可信重定向

A.7.14. Trusted Redirection

图像
图像

A.7.15. 代理委派

A.7.15. Deputized Delegation

图像
图像

A.7.16. 代理重定向

A.7.16. Deputized Redirection

图像
图像

A.8. 中间模式定义

A.8. Intermediate Pattern Definitions

在这里,您将找到第 6 章中介绍的模式的定义。这些是演示先前模式实例中的合成缩减规则的初始模式。同样,每个定义都与其等效的 PIN 图一起显示。

Here you will find the definitions for the patterns presented in Chapter 6. These are the initial patterns demonstrating composition reduction rules from prior pattern instances. Again, each definition is presented along side its equivalent PIN diagram.

A.8.1. Fulfill 方法

A.8.1. Fulfill Method

图像
图像

A.8.2. 检索新

A.8.2. Retrieve New

图像
图像

A.8.3. 检索共享

A.8.3. Retrieve Shared

图像
图像

A.8.4. 对象化程序

A.8.4. Objectifier

图像
图像

A.8.5. 对象递归

A.8.5. Object Recursion

图像
图像

A.9. 四人组模式定义

A.9. Gang of Four Pattern Definitions

最后,本节介绍了第 7 章中讨论的 Gang of Four 模式中的每种模式的定义和 PIN 图。

Finally, this section presents definitions and PIN diagrams for each of the Gang of Four patterns discussed in Chapter 7.

A.9.1. 抽象工厂

A.9.1. Abstract Factory

图像
图像

A.9.2. 工厂方法

A.9.2. Factory Method

图像
图像

A.9.3. 装饰器

A.9.3. Decorator

图像
图像

A.9.4. 代理

A.9.4. Proxy

图像
图像

A.9.5. 责任链

A.9.5. Chain of Responsibility

图像
图像

A.9.6. 模板方法

A.9.6. Template Method

图像
图像

书目

Bibliography

[1] 马丁·阿巴迪和卢卡·卡德利。对象理论。施普林格出版社,纽约,1996 年。

[1] Martín Abadi and Luca Cardelli. A Theory of Objects. Springer-Verlag, New York, 1996.

[2] 李·阿克曼 (Lee Ackerman) 和塞尔索·冈萨雷斯 (Celso Gonzalez)。基于模式的工程:通过 Patterns 成功交付解决方案。Addison-Wesley,波士顿,2010 年。

[2] Lee Ackerman and Celso Gonzalez. Patterns-Based Engineering: Successfully Delivering Solutions via Patterns. Addison-Wesley, Boston, 2010.

[3] 克里斯托弗·亚历山大、萨拉·石川和默里·西尔弗斯坦。模式语言:城镇、建筑、施工。牛津大学出版社,纽约,1977 年。

[3] Christopher Alexander, Sara Ishikawa, and Murray Silverstein. A Pattern Language: Towns, Building, Construction. Oxford University Press, New York, 1977.

[4] 克里斯托弗·亚历山大。关于形式综合的笔记(第 15 次印刷)。牛津大学出版社,纽约,1964 年、1999 年。

[4] Christopher W. Alexander. Notes on the Synthesis of Form (15th printing). Oxford University Press, New York, 1964, 1999.

[5] 肯特·贝克。Smalltalk 最佳实践模式。Prentice Hall,新泽西州上马鞍河,1997 年。

[5] Kent Beck. Smalltalk Best Practice Patterns. Prentice Hall, Upper Saddle River, NJ, 1997.

[6] 皮特·贝克尔。工作草案,编程语言 c++ 的标准。技术报告 N3242,ISO/IEC JTC/SC22/第 21 工作组,2011 年。

[6] Pete Becker. Working draft, standard for programming language c++. Technical Report N3242, ISO/IEC JTC/SC22/Working Group 21, 2011.

[7] James M. Bieman 和 Byung-Kyoo Kang。面向对象的系统中的内聚和重用。在 ACM 软件可重用性研讨会论文集,SSR'95,第 259-262 页,1995 年 4 月。转载于 1995 年 8 月的 ACM Software Engineering Notes。

[7] James M. Bieman and Byung-Kyoo Kang. Cohesion and reuse in an object-oriented system. In Proceedings of the ACM Symposium on Software Reusability, SSR’95, pp. 259–262, Apr 1995. Reprinted in ACM Software Engineering Notes, Aug 1995.

[8] James M. Bieman 和 Byung-Kyoo Kang。测量设计级的内聚力。IEEE 软件工程汇刊, 24(2):111–124, 1998.

[8] James M. Bieman and Byung-Kyoo Kang. Measuring design-level cohesion. IEEE Transactions on Software Engineering, 24(2):111–124, 1998.

[9] 詹姆斯·比曼和琳达·奥特。测量功能内聚力。IEEE 软件工程汇刊, 20(8):644–657, 1994.

[9] James M. Bieman and Linda Ott. Measuring functional cohesion. IEEE Transactions on Software Engineering, 20(8):644–657, 1994.

[10] 格雷迪·布奇。部落记忆。IEEE 软件,25(2):16–17,2008 年。

[10] Grady Booch. Tribal memory. IEEE Software, 25(2):16–17, 2008.

[11] 格雷迪·布赫和塞尔索·冈萨雷斯。软件架构手册。www.handbookofsoftwarearchitecture.com/,2010 年 10 月。

[11] Grady Booch and Celso Gonzalez. Handbook of software architecture. www.handbookofsoftwarearchitecture.com/, Oct 2010.

[12] 扬·博世。将模式设计为语言结构。面向对象编程杂志,1(2):18–52,1998 年 5 月。

[12] Jan Bosch. Design patterns as language constructs. Journal of Object Oriented Programming, 1(2):18–52, May 1998.

[13] LC Briand 和 JW Daly。面向对象系统中内聚力测量的统一框架。在第四次 METRICS'97 会议论文集,第 43-53 页,1997 年 11 月。

[13] L. C. Briand and J. W. Daly. A unified framework for cohesion measurement in object-oriented systems. In Proceedings of the Fourth Conference on METRICS’97, pp. 43–53, Nov 1997.

[14] 威廉·布朗、拉斐尔·马尔沃、威廉·布朗、W·麦考密克·海斯三世和托马斯·莫布雷。反模式:在危机中重构软件、架构和项目。Wiley,纽约,1998 年。

[14] William J. Brown, Raphael C. Malveau, William H. Brown, W. McCormick Hays III, and Thomas J. Mowbray. Anti-Patterns: Refactoring Software, Architectures, and Projects in Crisis. Wiley, New York, 1998.

[15] Shyam R. Chidamber 和 Chris F. Kemerer。迈向面向对象设计的指标套件。在 OOPSLA '91 论文集,第 197-211 页。ACM,1991 年。

[15] Shyam R. Chidamber and Chris F. Kemerer. Towards a metrics suite for object-oriented design. In Proceedings of OOPSLA ’91, pp. 197–211. ACM, 1991.

[16] Shyam R. Chidamber 和 Chris F. Kemerer。用于面向对象设计的指标套件。IEEE 软件工程汇刊, 20(6):476–493, 1994.

[16] Shyam R. Chidamber and Chris F. Kemerer. A metrics suite for object-oriented design. IEEE Transactions on Software Engineering, 20(6):476–493, 1994.

[17] 詹姆斯·科普林。C++ 习语。1998 年,第三届欧洲编程与计算模式语言会议论文集

[17] James Coplien. C++ idioms. In Proceedings of the Third European Conference on Pattern Languages of Programming and Computing, 1998.

[18] C++ 标准委员会。工作草案,编程语言 C++ 标准,第 20.7.2.2 节 [util.smartptr.shared]。ISO/IEC 文档 N3242=11-0012,2011 年 2 月。

[18] C++ Standards Committee. Working Draft, Standard for Programming Language C++, Section 20.7.2.2 [util.smartptr.shared]. ISO/IEC Document N3242=11-0012, Feb 2011.

[19] 马丁·福勒。重构:改进现有代码的设计。Addison-Wesley,波士顿,1999 年。

[19] Martin Fowler. Refactoring: Improving the Design of Existing Code. Addison-Wesley, Boston, 1999.

[20] 马丁·福勒和肯德尔·斯科特。UML Distilled: A Brief Guide to the Standard Object Modeling Language,第 3 版。Addison-Wesley,波士顿,2003 年。

[20] Martin Fowler and Kendall Scott. UML Distilled: A Brief Guide to the Standard Object Modeling Language, 3rd ed. Addison-Wesley, Boston, 2003.

[21] 埃里希·伽马、理查德·赫尔姆、拉尔夫·约翰逊和约翰·弗利赛德。设计模式。Addison-Wesley,波士顿,1995 年。

[21] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns. Addison-Wesley, Boston, 1995.

[22] Byung-Kyoo Kang 和 James M. Bieman。设计级凝聚力度量:推导、比较和应用。第 20 届国际计算机软件和应用会议 (COMPSAC'96) 论文集,第 92-97 页,1996 年 8 月。

[22] Byung-Kyoo Kang and James M. Bieman. Design-level cohesion measures: Derivation, comparison, and applications. In Proceedings of the 20th International Computer Software and Applications Conference (COMPSAC’96), pp. 92–97, Aug 1996.

[23] Byung-Kyoo Kang 和 James M. Bieman。使用 Design Coheance 对软件进行可视化、量化和重构。在第八届软件工程与知识工程国际会议上,SEKE '96,1996 年 6 月。

[23] Byung-Kyoo Kang and James M. Bieman. Using design cohesion to visualize, quantify and restructure software. In Eighth International Conference of Software Engineering and Knowledge Engineering, SEKE ’96, Jun 1996.

[24] 约书亚·克里耶夫斯基。重构为模式。Addison-Wesley,波士顿,2005 年。

[24] Joshua Kerievsky. Refactoring to Patterns. Addison-Wesley, Boston, 2005.

[25] Howard C. Lovatt、Anthony M. Sloane 和 Dominic R. Verity。适用于 Java 的模式强制编译器 (PEC):使用编译器。在 Sven Hartmann 和 Markus Stumptner 编辑的《信息技术研究与实践会议》中,第 43 卷。出现在 2005 年第二届亚太概念建模会议 (APCCM2005) 上。

[25] Howard C. Lovatt, Anthony M. Sloane, and Dominic R. Verity. A pattern enforcing compiler (PEC) for Java: Using the compiler. In Sven Hartmann and Markus Stumptner, eds., Conferences in Research and Practice in Information Technology, vol. 43. Appeared at the Second Asia-Pacific Conference on Conceptual Modeling (APCCM2005), 2005.

[26] O. L. Madsen、B. Møller-Pederson 和 K. Nygaard。BETA 语言中的面向对象编程。Addison-Wesley,波士顿,1993 年。

[26] O. L. Madsen, B. Møller-Pederson, and K. Nygaard. Object-Oriented Programming in the BETA Language. Addison-Wesley, Boston, 1993.

[27] 罗伯特·马丁。Clean Code: A Handbook of Agile Software Craftsmanship(《简洁代码:敏捷软件工艺手册》)。Prentice Hall PTR,新泽西州上马鞍河,2008 年。

[27] Robert C. Martin. Clean Code: A Handbook of Agile Software Craftsmanship. Prentice Hall PTR, Upper Saddle River, NJ, 2008.

[28] 斯科特·迈耶斯。有效的 C++。Addison-Wesley,波士顿,1992 年。

[28] Scott Meyers. Effective C++. Addison-Wesley, Boston, 1992.

[29] 斯科特·迈耶斯。更有效的 C++。Addison-Wesley,波士顿,1996 年。

[29] Scott Meyers. More Effective C++. Addison-Wesley, Boston, 1996.

[30] 约尔格·尼尔、洛塔尔·温德哈尔斯和阿尔伯特·尊多夫。一种交互式且可扩展的设计模式恢复方法。技术报告 tr-ri-03-236,帕德博恩大学,德国帕德博恩,2003 年 1 月。

[30] Jörg Niere, Lothar Wendehals, and Albert Zündorf. An interactive and scalable approach to design pattern recovery. Technical Report tr-ri-03-236, University of Paderborn, Paderborn, Germany, Jan 2003.

[31] S. Patel, W. Chu, and R. Baxter.复合模块内聚的度量。在软件工程国际会议上,第 38-48 页,1992 年 5 月。

[31] S. Patel, W. Chu, and R. Baxter. A measure for composite module cohesion. In International Conference on Software Engineering, pp. 38–48, May 1992.

[32] 德克·里勒。复合设计模式。在 1997 年 ACM SIGPLAN 面向对象编程系统、语言和应用程序会议记录中,第 218-228 页。ACM 出版社,1997 年。

[32] Dirk Riehle. Composite design patterns. In Proceedings of the 1997 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Languages and Applications, pp. 218–228. ACM Press, 1997.

[33] 詹姆斯·伦博、伊瓦尔·雅各布森和格雷迪·布奇。统一建模语言参考手册,第 2 版,Addison-Wesley,波士顿,2004 年。

[33] James Rumbaugh, Ivar Jacobson, and Grady Booch. The Unified Modeling Language Reference Manual, 2nd ed., Addison-Wesley, Boston, 2004.

[34] M. H. Samadzadeh 和 S. J. Khan。面向对象的软件系统的稳定性、耦合性和内聚性。第 22 届年度 ACM 计算机科学会议论文集,第 312-319 页,1994 年 3 月 8 日至 10 日。

[34] M. H. Samadzadeh and S. J. Khan. Stability, coupling and cohesion of object-oriented software systems. In Proceedings of the 22nd Annual ACM Computer Science Conference on Scaling Up, pp. 312–319, Mar 8–10, 1994.

[35] 杰森·麦克·史密斯。SPQR:从源代码中自动检测设计模式的形式基础和实际支持。博士论文,北卡罗来纳大学教堂山分校,2005 年 12 月。

[35] Jason McC. Smith. SPQR: Formal Foundations and Practical Support for the Automated Detection of Design Patterns from Source Code. Ph.D. thesis, University of North Carolina at Chapel Hill, Dec 2005.

[36] 杰森·麦克·史密斯。Pattern Instance Notation:用于软件模式的动态可视化和理解的简单分层视觉符号。视觉语言与计算杂志,22(5):355–374,2011 年。

[36] Jason McC. Smith. The Pattern Instance Notation: A simple hierarchical visual notation for the dynamic visualization and comprehension of software patterns. Journal of Visual Languages and Computing, 22(5):355–374, 2011.

[37] 杰森·麦克·史密斯和大卫·斯托茨。SPQR:从源代码中灵活地自动提取设计模式。第 18 届 IEEE 自动化软件工程国际会议,第 215-224 页,2003 年 10 月。

[37] Jason McC. Smith and David Stotts. SPQR: Flexible automated design pattern extraction from source code. In 18th IEEE International Conference on Automated Software Engineering, pp. 215–224, Oct 2003.

[38] 杰森·麦克·史密斯和大卫·斯托茨。使用 SPQR 的面向意图的设计模式形式化第 7 章。IDEA Group,2007 年。

[38] Jason McC. Smith and David Stotts. Intent-Oriented Design Pattern Formalization Using SPQR, chapter 7. IDEA Group, 2007.

[39] 马克吐温。汤姆索亚历险记。古腾堡出版社,www.gutenberg.org/ebooks/74,2005 年。

[39] Mark Twain. The Adventures of Tom Sawyer. Gutenberg Press, www.gutenberg.org/ebooks/74, 2005.

[40] 鲍比·伍尔夫。抽象类模式。在 Neil Harrison、Brian Foote 和 Hans Rohnert 编辑的《程序设计的模式语言 4》中。Addison-Wesley,波士顿,1998 年。

[40] Bobby Woolf. The abstract class pattern. In Neil Harrison, Brian Foote, and Hans Rohnert, eds., Pattern Languages of Program Design 4. Addison-Wesley, Boston, 1998.

[41] 鲍比·伍尔夫。对象递归模式。在 Neil Harrison、Brian Foote 和 Hans Rohnert 编辑的《程序设计的模式语言 4》中。Addison-Wesley,波士顿,1998 年,41-52。

[41] Bobby Woolf. The object recursion pattern. In Neil Harrison, Brian Foote, and Hans Rohnert, eds., Pattern Languages of Program Design 4. Addison-Wesley, Boston, 1998, 41–52.

[42] E. Yourdon 和 L. Constantine。结构化设计。Prentice Hall,新泽西州恩格尔伍德悬崖,1979 年。

[42] E. Yourdon and L. Constantine. Structured Design. Prentice Hall, Englewood Cliffs, NJ, 1979.

[43] 沃尔特·季默。设计模式之间的关系。在 James O. Coplien 和 Douglas C. Schmidt 编辑的《程序设计的模式语言》中。Addison-Wesley,波士顿,1995 年,345-364。

[43] Walter Zimmer. Relationships between design patterns. In James O. Coplien and Douglas C. Schmidt, eds., Pattern Languages of Program Design. Addison-Wesley, Boston, 1995, 345–364.

指数

Index

一个

A

马丁·阿巴迪,281

Abadi, Martín, 281

抽象工厂模式

Abstract Factory pattern

作为合并的 PINbox, 65

as coalesced PINbox, 65

作为 collapsed PINbox, 50

as collapsed PINbox, 50

作为扩展的 PINbox,64

as expanded PINbox, 64

作为 stacked PINbox, 60

as stacked PINbox, 60

饰演 UML, 59

as UML, 59

描述,260-263

description, 260263

Rho 微积分,313

rho-calculus, 313

抽象接口模式

Abstract Interface pattern

适用性,141-142

applicability, 141142

作为 Fulfill Method 模式的一部分,6872

as part of Fulfill Method pattern, 6872

合作, 142

collaborations, 142

后果,142-143

consequences, 142143

创建对象,43-44

creating objects, 4344

实施,143-144

implementation, 143144

意图,140

intent, 140

同位素形式,75

isotopic forms, 75

动机,140-141

motivation, 140141

参与者,142

participants, 142

相关模式, 144

related patterns, 144

Rho 微积分, 298

rho-calculus, 298

结构, 142

structure, 142

抽象密度,EDP,110112

Abstraction density, EDPs, 110112

李·阿克曼,9

Ackerman, Lee, 9

克里斯托弗·亚历山大,2486

Alexander, Christopher, 2, 4, 86

动物示例

Animal example

吃。请参阅 Objectifier 模式

eating. See Objectifier pattern.

移动。请参见抽象接口模式

moving. See Abstract Interface pattern.

反模式, 91

Anti-patterns, 91

数组排序示例。请参阅 递归模式

Array sorting example. See Recursion pattern.

异步执行,146

Asynchronous execution, 146

B

B

肯特·贝克,32-33

Beck, Kent, 3233

行为模式,273-279

Behavioral patterns, 273279

格雷迪·布赫

Booch, Grady

模式集合,4

pattern collections, 4

部落知识,5-8

tribal knowledge, 58

书籍和出版物

Books and publications

Clean Code,32

Clean Code, 32

设计模式:元素..., 2

Design Patterns: Elements of..., 2

重构92

Refactoring, 92

重构为模式92

Refactoring to Patterns, 92

C

C

C 代码示例,118120121193246

C code examples, 118, 120, 121, 193, 246

C# 代码示例,178

C# code examples, 178

C++ 代码示例:26293136125128138143148153160166168173176184190193195201210213216220222227233238247

C++ code examples, 26, 29, 31, 36, 125, 128, 138, 143, 148, 153, 160, 166168, 173, 176, 184, 190, 193195, 201, 210, 213, 216, 220, 222, 227, 233, 238, 247

调用。请参阅 委派模式

Calls. See Delegation pattern.

卢卡·卡德利,281

Cardelli, Luca, 281

模式定义的类别,22

Categories of pattern definition, 22

CEO 示例36另请参阅 委派模式

CEO example, 36. See also Delegation pattern.

责任链模式

Chain of Responsibility pattern

描述,273-275

description, 273275

Rho 微积分, 318

rho-calculus, 318

化学同位素,72-74

Chemical isotopes, 7274

柴郡猫技术,119

Cheshire cat technique, 119

表达清晰度,EDP,109-110

Clarity of expression, EDPs, 109110

Classes

分解模式,25-26

decomposing patterns, 2526

物化类似行为,244另请参阅 Objectifier 模式

objectifying similar behaviors, 244. See also Objectifier pattern.

Clean Code,32

Clean Code, 32

合并密码盒,6265

Coalescing PINboxes, 6265

内聚力

Cohesion

EDP,43-44

EDPs, 4344

对象, 35

objects, 35

折叠的密码框,4951

Collapsed PINboxes, 4951

Common Lisp 对象系统 (CLOS),10

Common Lisp Object System (CLOS), 10

撰写 EDP,68-77

Composing EDPs, 6877

作曲,rho 微积分,291-293

Composition, rho-calculus, 291293

砾岩模式。另请参见委托式聚合模式

Conglomeration pattern. See also Delegated Conglomeration pattern.

适用性,161

applicability, 161

作为工厂方法模式的一部分,263-265

as part of Factory Method pattern, 263265

作为模板模式的一部分,275279

as part of Template pattern, 275279

合作, 162

collaborations, 162

后果,162

consequences, 162

设计空间,35-38

design space, 3538

实施,162

implementation, 162

意图,159

intent, 159

方法调用分类,163164

method call classification, 163164

动机,159-161

motivation, 159161

参与者,161-162

participants, 161162

相关模式,162163

related patterns, 162163

Rho 微积分,300

rho-calculus, 300

结构, 161

structure, 161

背景,EDP,30-33

Context, EDPs, 3033

上下文类别,22

Context category, 22

Create Object 模式

Create Object pattern

适用性,122

applicability, 122

作为抽象工厂模式的一部分,261263

as part of Abstract Factory pattern, 261263

作为工厂方法模式的一部分,263-265

as part of Factory Method pattern, 263265

合作, 123

collaborations, 123

后果,124

consequences, 124

实施,124-125

implementation, 124125

意图,117

intent, 117

动机,117-122

motivation, 117122

参与者,123

participants, 123

相关模式, 125

related patterns, 125

Rho 微积分,295-296

rho-calculus, 295296

结构, 123

structure, 123

创造模式,260-265

Creational patterns, 260265

D

D

数据协议回退示例。请参见 Revert Method 模式

Data protocol fallback example. See Revert Method pattern.

Decomposing 消息。请参见 聚集模式

Decomposing Message. See Conglomeration pattern.

分解模式

Decomposing patterns

模式定义的类别, 22

categories of pattern definition, 22

班级,25-26

classes, 2526

上下文类别,22

context category, 22

示例,装饰器模式,1821

example, Decorator pattern, 1821

田,25-30

fields, 2530

进球,22

goal of, 22

方法,25-30

methods, 2530

最小尺寸:21-30

minimum size, 2130

面向对象编程, 实体

object-oriented programming, entity

互动,28

interactions, 28

对象,25-30

objects, 2530

问题类别,22

problem category, 22

范围界定,22-24

scoping, 2224

解决方案类别,22

solution category, 22

类型,25-30

types, 2530

未命名命名空间, 24

unnamed namespaces, 24

装饰器模式

Decorator pattern

作为协作 UML 元素,47

as collaboration UML element, 47

作为扩展的 PINbox,57

as expanded PINbox, 57

作为 PINbox, 52

as PINbox, 52

描述,265-269

description, 265269

Rho 微积分, 316

rho-calculus, 316

装饰器模式,示例

Decorator pattern, examples

分解模式,18-21

decomposing patterns, 1821

重建,77-90

recreating, 7790

Defer 实现模式。请参见抽象接口模式

Defer Implementation pattern. See Abstract Interface pattern.

委托 Conglomeration 模式。另见 砾岩模式;委托模式;代理委托模式;受信任的委派模式

Delegated Conglomeration pattern. See also Conglomeration pattern; Delegation pattern; Deputized Delegation pattern; Trusted Delegation pattern.

适用性,189

applicability, 189

合作, 190

collaborations, 190

后果,190

consequences, 190

实施,190-191

implementation, 190191

意图,187

intent, 187

方法调用分类,191192

method call classification, 191192

动机,187-189

motivation, 187189

参与者,189-190

participants, 189190

相关模式, 191

related patterns, 191

Rho 微积分,303

rho-calculus, 303

结构, 189

structure, 189

委派模式。另请参见委托式集合模式;代理委托模式;受信任的委派模式

Delegation pattern. See also Delegated Conglomeration pattern; Deputized Delegation pattern; Trusted Delegation pattern.

适用性,147

applicability, 147

合作, 148

collaborations, 148

后果,148

consequences, 148

设计空间,35-38

design space, 3538

实施,148

implementation, 148

意图,145

intent, 145

方法调用分类,149150

method call classification, 149150

动机,145-146

motivation, 145146

参与者,147

participants, 147

相关模式, 149

related patterns, 149

Rho 微积分, 299

rho-calculus, 299

结构, 147

structure, 147

委派模式

Delegation patterns

授权集会,187-192

Delegated Conglomeration, 187192

代表团145-150

Delegation, 145150

代表代表团216-221

Deputized Delegation, 216221

受信任的委派200-208

Trusted Delegation, 200208

代理委派模式。另请参见委托式集合模式;委托模式;受信任的委派模式

Deputized Delegation pattern. See also Delegated Conglomeration pattern; Delegation pattern; Trusted Delegation pattern.

适用性,218

applicability, 218

合作, 219

collaborations, 219

后果,219-220

consequences, 219220

实施,220

implementation, 220

意图,216

intent, 216

方法调用分类,220221

method call classification, 220221

动机,216-218

motivation, 216218

参与者,218219

participants, 218219

相关模式, 220

related patterns, 220

Rho 微积分,306

rho-calculus, 306

结构, 219

structure, 219

代理重定向模式。另请参阅重定向递归模式;重定向模式;可信重定向模式

Deputized Redirection pattern. See also Redirected Recursion pattern; Redirection pattern; Trusted Redirection pattern.

适用性,224

applicability, 224

作为代理模式的一部分,269-273

as part of Proxy pattern, 269273

合作, 226

collaborations, 226

后果,227

consequences, 227

实施,227

implementation, 227

意图,222

intent, 222

方法调用分类,227228

method call classification, 227228

动机,222-224

motivation, 222224

参与者,224226

participants, 224226

相关模式, 227

related patterns, 227

Rho-微积分, 307

rho-calculus, 307

结构,226

structure, 226

设计模式。另请参阅 EDP (Elemental Design Patterns) (基本设计模式)。

Design patterns. See also EDPs (Elemental Design Patterns).

反模式, 91

anti-patterns, 91

收藏,4

collections of, 4

定义,21

definition, 21

图表。请参阅 PIN (模式实例表示法) ;PINbox

diagramming. See PIN (Pattern Instance Notation); PINboxes.

医源性, 91

iatrogenic, 91

实例表示法和角色,293295

instance notation and roles, 293295

恶性,91

malignant, 91

部分,91

partial, 91

认出,15-17

recognizing, 1517

死记硬背9-10

as rote, 910

设计模式, 历史

Design patterns, history of

文档,5-8

documentation, 58

创始人, 2,4

founders, 2, 4

语言相关视图,1012

language-dependent views, 1012

从神话到科学,12

from myth to science, 12

模式集合,4

pattern collections, 4

模式死记硬背9-10

patterns as rote, 910

自我意识设计,2-3

selfconscious design, 23

开创性作品,2

seminal works, 2

部落知识,5-8

tribal knowledge, 58

设计类型,2-3

types of design, 23

无意识的设计,2-3

unselfconscious design, 23

设计模式:元素..., 2

Design Patterns: Elements of..., 2

设计空间,EDP,33-42

Design space, EDPs, 3342

绘制设计模式图。请参阅 PIN (模式实例表示法) ;PINbox

Diagramming design patterns. See PIN (Pattern Instance Notation); PINboxes.

文档

Documentation

设计模式的历史,5-8

history of design patterns, 58

和培训,108-109

and training, 108109

点运算符,283

Dot operators, 283

双重调度,10

Double-dispatch, 10

D 指针技术,119

d-pointer technique, 119

E

E

EDP(基本设计模式)。另请参阅 设计模式

EDPs (Elemental Design Patterns). See also Design patterns.

提取密度,110112

abstraction density, 110112

反模式, 91

anti-patterns, 91

背景,14-17

background, 1417

分解成更小的部分。请参阅分解模式

breaking into smaller parts. See Decomposing patterns.

表达的清晰度,109-110

clarity of expression, 109110

凝聚力,43-44

cohesion, 4344

与其他模式结合,101105

combining with other patterns, 101105

组合模式,68-77

composing patterns, 6877

概念与结构,16

concepts vs. constructs, 16

背景,30-33

context, 3033

定义,18

definition, 18

设计空间,33-42

design space, 3342

文档, 108109

documentation, 108109

字段使用情况,42-44

field usage, 4244

形式主义,287-291

formalisms, 287291

医源性模式,91

iatrogenic patterns, 91

同位素,72-77

isotopes, 7277

恶性模式,91

malignant patterns, 91

方法调用依赖,2933

method call reliance, 2933

方法相似性,32

method similarity, 32

指标,109112

metrics, 109112

部分设计模式,91

partial design patterns, 91

模式规范,16-17

pattern specifications, 1617

程序分析,112

procedural analysis, 112

识别模式,15-17

recognizing patterns, 1517

重新创建 Decorator,示例,7790

recreating Decorator, example, 7790

重构,91-100

refactoring, 91100

信实,29-33,42-44

reliances, 2933, 4244

rho-calculus 的参见 Rho 演算、EDP 定义

rho-calculus. See Rho-calculus, EDP definitions.

SPQR(模式查询和识别系统),14-17

SPQR (System for Pattern Query and Recognition), 1417

状态变化,42-44

state changes, 4244

培训,108-109

training, 108109

独特特征,13-14

unique traits, 1314

使用者关系,101-102

used-by relationships, 101102

EDP(基本设计模式)、对象

EDPs (Elemental Design Patterns), objects

抽象界面模式,4344

Abstract Interface pattern, 4344

凝聚力,35

cohesion, 35

创造,43-44

creating, 4344

分解模式,25-30

decomposing patterns, 2530

等价,34-35

equivalence, 3435

继承模式,43

Inheritance pattern, 43

实例化,4344

instantiating, 4344

封装

Encapsulation

相似性,32

similarity, 32

的数据。请参见创建对象模式

of data. See Create Object pattern.

设计之类,101

of design, 101

等价,对象,34-35

Equivalence, objects, 3435

执行模式。请参阅 委派模式

Executive pattern. See Delegation pattern.

展开的密码盒,5556

Expanded PINboxes, 5556

Extend Method 模式

Extend Method pattern

适用性,183

applicability, 183

作为 Decorator 模式的一部分,8386268269

as part of Decorator pattern, 8386, 268269

作为责任链模式的一部分,273-275

as part of Chain of Responsibility pattern, 273275

合作, 184

collaborations, 184

后果,184

consequences, 184

实施,184-185

implementation, 184185

意图,181

intent, 181

方法调用分类,185186

method call classification, 185186

动机,181-182

motivation, 181182

参与者,183

participants, 183

相关模式, 185

related patterns, 185

Rho 微积分,302

rho-calculus, 302

结构, 183

structure, 183

扩展 Super 模式。请参阅 Extend Method 模式

Extending Super pattern. See Extend Method pattern.

F

F

Factory Method 模式描述,263265

Factory Method pattern description, 263265

栅栏柱错误,132134

Fencepost error, 132134

字段使用,EDP,42-44

Field usage, EDPs, 4244

场,分解模式,25-30

Fields, decomposing patterns, 2530

蝇量级模式作为 PINbox,53

Flyweight pattern as PINbox, 53

模式的形式主义,105-108另请参见 Rho 微积分

Formalism of patterns, 105108. See also Rho-calculus.

创始人,设计模式的历史,2,4另请参阅 特定名称

Founders, history of design patterns, 2, 4. See also specific names.

马丁·福勒,92

Fowler, Martin, 92

Fulfill Method 模式

Fulfill Method pattern

适用性,231

applicability, 231

作为抽象工厂模式的一部分,261263

as part of Abstract Factory pattern, 261263

作为工厂方法模式的一部分,263-265

as part of Factory Method pattern, 263265

作为 Objectifier 模式的一部分,7779-80248-249

as part of Objectifier pattern, 77, 7980, 248249

作为代理模式的一部分,269-273

as part of Proxy pattern, 269273

作为模板模式的一部分,275279

as part of Template pattern, 275279

合作, 232

collaborations, 232

后果,233

consequences, 233

实施,233-234

implementation, 233234

意图,231

intent, 231

动机,231

motivation, 231

参与者,231

participants, 231

相关模式, 234

related patterns, 234

Rho-微积分, 308

rho-calculus, 308

结构, 232

structure, 232

G

G

埃里希·伽玛,2

Gamma, Erich, 2

四人组模式

Gang of Four patterns

抽象工厂,260-263,313

Abstract Factory, 260263, 313

行为的,273-279

behavioral, 273279

责任链,273-275,318

Chain of Responsibility, 273275, 318

创造论,260-265

creational, 260265

装饰者,265-269,316

Decorator, 265269, 316

工厂方法,263-265,314-315

Factory Method, 263265, 314315

代理,269-273,317

Proxy, 269273, 317

Rho 微积分,313-319

rho-calculus, 313319

结构,265-273

structural, 265273

模板方法275279,319

Template Method, 275279, 319

冈萨雷斯,塞尔索,4,9

Gonzalez, Celso, 4, 9

H

H

赫尔姆,理查德,2

Helm, Richard, 2

Helper 方法。请参见 聚集模式

Helper Methods. See Conglomeration pattern.

设计模式的历史

History of design patterns

文档,5-8

documentation, 58

创始人,2,4另请参阅 特定名称

founders, 2, 4. See also specific names.

语言相关视图,1012

language-dependent views, 1012

从神话到科学,12

from myth to science, 12

模式集合,4

pattern collections, 4

模式死记硬背9-10

patterns as rote, 910

自我意识设计,2-3

selfconscious design, 23

开创性作品,2

seminal works, 2

部落知识,5-8

tribal knowledge, 58

设计类型,2-3

types of design, 23

无意识的设计,2-3

unselfconscious design, 23

I

医源性模式,9-10,91

Iatrogenic patterns, 910, 91

继承, 倍数, 136

Inheritance, multiple, 136

继承模式

Inheritance pattern

适用性,135

applicability, 135

作为 Fulfill Method 模式的一部分,6872

as part of Fulfill Method pattern, 6872

合作, 135

collaborations, 135

后果,135-138

consequences, 135138

创建对象,43

creating objects, 43

实施,138-139

implementation, 138139

意图,130

intent, 130

同位素形式,76

isotopic forms, 76

动机,130-134

motivation, 130134

参与者,135

participants, 135

相关模式, 138

related patterns, 138

Rho 微积分, 298

rho-calculus, 298

结构, 135

structure, 135

实例,创建,235-239另请参阅 检索新模式.

Instances, creating, 235239. See also Retrieve New pattern.

实例化对象,4344

Instantiating objects, 4344

实例化模式。请参见创建对象模式

Instantiation pattern. See Create Object pattern.

IsA 模式。请参阅继承模式

IsA pattern. See Inheritance pattern.

同位素,72-77,285-286

Isotopes, 7277, 285286

J

J

Java 代码示例,273638126127129133134141144145152156162169184187188197206233243

Java code examples, 27, 36, 38, 126, 127, 129, 133134, 141, 144, 145, 152, 156, 162, 169, 184, 187, 188, 197, 206, 233, 243

拉尔夫·约翰逊,2

Johnson, Ralph, 2

K

K

约书亚·克里耶夫斯基,92

Kerievsky, Joshua, 92

L

L

语言成语, 11

Language idioms, 11

语言相关模式,10-12

Language-dependent patterns, 1012

M

M

恶性模式,91

Malignant patterns, 91

罗伯特·马丁,32

Martin, Robert, 32

模式数学,105-108另请参见 Rho 微积分

Mathematics of patterns, 105108. See also Rho-calculus.

内存管理。请参阅对象管理

Memory management. See Object management.

消息传送模式。请参阅 委派模式

Messaging pattern. See Delegation pattern.

方法调用依赖,2933

Method call reliance, 2933

方法调用模式。请参阅 委派模式

Method Invocation pattern. See Delegation pattern.

方法调用模式

Method Invocation patterns

聚集,159-164

Conglomeration, 159164

授权集会,187-192

Delegated Conglomeration, 187192

代表团145-150

Delegation, 145150

代表代表团216-221

Deputized Delegation, 216221

代理重定向222-228

Deputized Redirection, 222228

扩展方法181186

Extend Method, 181186

递归165-171

Recursion, 165171

重定向递归193199

Redirected Recursion, 193199

重定向151-158

Redirection, 151158

还原方法172-180

Revert Method, 172180

受信任的委派200-208

Trusted Delegation, 200208

可信重定向209215

Trusted Redirection, 209215

方法

Methods

分解模式,25-30

decomposing patterns, 2530

命名,32-33

naming, 3233

超负荷, 32

overloaded, 32

先前摘录,实施,231-234另请参阅 Fulfill Method 模式

previously extracted, implementing, 231234. See also Fulfill Method pattern.

相似性,32

similarity, 32

EDP 的指标,109-112

Metrics for EDPs, 109112

苔藓示例,7

Moss example, 7

斯科特·迈耶斯,124

Meyers, Scott, 124

多重性连接、PIN 框、5662

Multiplicity connections, PINboxes, 5662

N

N

命名空间,未命名,24

Namespaces, unnamed, 24

命名约定,18

Naming conventions, 18

O

O

对象元素模式

Object Elements patterns

创建对象117125

Create Object, 117125

对象管理

Object management

C++ shared_ptr,240

C++ shared_ptr, 240

C++ 非托管,235

C++ unmanaged, 235

垃圾收集, 235238240243

garbage collection, 235, 238, 240, 243

Objective-C autorelease240

Objective-C autorelease, 240

Object Recursion 模式。另请参见递归模式;重定向递归模式

Object Recursion pattern. See also Recursion pattern; Redirected Recursion pattern.

适用性,252-253

applicability, 252253

作为 Decorator 模式的一部分,8386268269

as part of Decorator pattern, 8386, 268269

合作,255-256

collaborations, 255256

后果,256

consequences, 256

实施,256-257

implementation, 256257

继承中,133

in Inheritance, 133

意图,251

intent, 251

动机,251-252

motivation, 251252

参与者,253

participants, 253

相关模式, 257

related patterns, 257

Rho 微积分, 312

rho-calculus, 312

结构,253,254-255

structure, 253, 254255

Objectifier 模式

Objectifier pattern

适用性,245-247

applicability, 245247

合作, 249

collaborations, 249

后果,249-250

consequences, 249250

实施,250

implementation, 250

意图,244

intent, 244

动机,244-245

motivation, 244245

参与者,249

participants, 249

相关模式, 250

related patterns, 250

Rho 微积分,311

rho-calculus, 311

结构,248-249

structure, 248249

Objective-C 代码示例,37131132137156190

Objective-C code examples, 37, 131, 132, 137, 156, 190

面向对象编程、实体交互、28

Object-oriented programming, entity interactions, 28

对象

Objects

抽象界面模式,4344

Abstract Interface pattern, 4344

凝聚力,35

cohesion, 35

“创建对象”模式,117125

Create Object pattern, 117125

创造,43-44

creating, 4344

分解模式,25-30

decomposing patterns, 2530

等价,34-35

equivalence, 3435

隐藏细节。请参阅代理模式

hiding details. See Proxy pattern.

继承模式,43

Inheritance pattern, 43

实例化,4344

instantiating, 4344

限制访问。请参阅代理模式

limiting access. See Proxy pattern.

检索模式,126129

Retrieve pattern, 126129

共享,引用但不拥有,240-243另请参阅检索共享模式

shared, referencing without owning, 240243. See also Retrieve Shared pattern.

差 1 误差,132134

Off-by-one error, 132134

不透明指针技术,119

Opaque pointer technique, 119

重写方法

Overriding a method

在继承中,133

in Inheritance, 133

需要。请参见抽象接口模式

requiring. See Abstract Interface pattern.

无需更换。请参阅 Extend Method 模式

without replacing. See Extend Method pattern.

P

P

Painter 示例。请参阅 重定向模式聚集模式

Painter example. See Redirection pattern, Conglomeration pattern.

伞兵示例。请参阅重定向递归模式

Paratroopers example. See Redirected Recursion pattern.

部分形态, 91

Partial patterns, 91

模式规范,16-17

Pattern specifications, 1617

模式。请参阅设计模式

Patterns. See Design patterns.

剥离 PIN 盒,6265

Peeling PINboxes, 6265

PIN (模式实例表示法),4549

PIN (Pattern Instance Notation), 4549

密码框

PINboxes

聚结,62-65

coalescing, 6265

折叠,49-51

collapsed, 4951

定义,49

definition, 49

展开,55-56

expanded, 5556

多重性,56-62

multiplicity, 5662

去皮,62-65

peeling, 6265

堆叠式,5662

stacked, 5662

标准,51-55

standard, 5155

指向实现的指针 (pimpl) 技术,119

Pointer-to-implementation (pimpl) technique, 119

多态性。参见 Abstract Interface pattern;Object Recursion 模式

Polymorphism. See Abstract Interface pattern; Object Recursion pattern.

多态性模式。请参见抽象接口模式

Polymorphism pattern. See Abstract Interface pattern.

问题类别,22

Problem category, 22

程序分析,EDP,112

Procedural analysis, EDPs, 112

代理模式

Proxy pattern

描述,269-273

description, 269273

Rho 微积分, 317

rho-calculus, 317

纯虚法,143-144

Pure virtual method, 143144

Python 代码示例,129138144162182197206233238

Python code examples, 129, 138, 144, 162, 182, 197, 206, 233, 238

R

R

递归,定义,165

Recursion, definition, 165

递归、模式

Recursion, patterns

对象递归251257

Object Recursion, 251257

递归165-171

Recursion, 165171

重定向递归193199

Redirected Recursion, 193199

递归模式。另请参阅 Object Recursion 模式;重定向递归模式

Recursion pattern. See also Object Recursion pattern; Redirected Recursion pattern.

适用性,168

applicability, 168

合作, 169

collaborations, 169

后果,169

consequences, 169

设计空间,35-38

design space, 3538

实施,169

implementation, 169

意图,165

intent, 165

方法调用分类,170171

method call classification, 170171

动机,165-168

motivation, 165168

参与者,168-169

participants, 168169

相关模式,169170

related patterns, 169170

Rho 微积分,301

rho-calculus, 301

结构, 169

structure, 169

重定向递归模式。另请参阅 代理重定向模式;对象递归模式;递归模式;重定向模式;可信重定向模式

Redirected Recursion pattern. See also Deputized Redirection pattern; Object Recursion pattern; Recursion pattern; Redirection pattern; Trusted Redirection pattern.

适用性,196

applicability, 196

作为责任链模式的一部分,273-275

as part of Chain of Responsibility pattern, 273275

合作, 197

collaborations, 197

后果,197

consequences, 197

设计空间, 40

design space, 40

实施,197-198

implementation, 197198

意图,193

intent, 193

方法调用分类,198199

method call classification, 198199

动机,193-196

motivation, 193196

参与者,197

participants, 197

相关模式, 198

related patterns, 198

Rho 微积分,303

rho-calculus, 303

结构, 196

structure, 196

重定向模式。另请参阅 代理重定向模式;重定向递归模式;可信重定向模式

Redirection pattern. See also Deputized Redirection pattern; Redirected Recursion pattern; Trusted Redirection pattern.

适用性,153-154

applicability, 153154

合作, 155

collaborations, 155

后果,155

consequences, 155

设计空间,35-38

design space, 3538

实施,156

implementation, 156

意图,151

intent, 151

方法调用分类,157158

method call classification, 157158

动机,151-153

motivation, 151153

参与者,154

participants, 154

相关模式, 157

related patterns, 157

Rho 微积分,300

rho-calculus, 300

结构, 154

structure, 154

重定向模式

Redirection patterns

代理重定向222-228

Deputized Redirection, 222228

重定向递归193199

Redirected Recursion, 193199

重定向151-158

Redirection, 151158

减少规则,285-286,291-293

Reduction rules, 285286, 291293

重构 EDP,91100

Refactoring EDPs, 91100

重构92

Refactoring, 92

重构为模式92

Refactoring to Patterns, 92

重构

Refactorings

EDP 之间,106

between EDPs, 106

提取法,92-93

Extract Method, 9293

移动方法,92959799

Move Method, 92, 95, 9799

Reliance 运算符,282-284

Reliance operators, 282284

信实,29-33,42-44

Reliances, 2933, 4244

请求、分布式处理。请参阅Object Recursion 模式

Requests, distributed handling. See Object Recursion pattern.

检索新模式

Retrieve New pattern

适用性,236

applicability, 236

合作,237-238

collaborations, 237238

后果,238

consequences, 238

实施,238-239

implementation, 238239

意图,235

intent, 235

动机,235-236

motivation, 235236

参与者,237

participants, 237

相关模式, 239

related patterns, 239

Rho-微积分, 309

rho-calculus, 309

结构,236-237

structure, 236237

检索模式

Retrieve pattern

适用性,127

applicability, 127

作为抽象工厂模式的一部分,261263

as part of Abstract Factory pattern, 261263

作为工厂方法模式的一部分,263-265

as part of Factory Method pattern, 263265

合作, 128

collaborations, 128

后果,128

consequences, 128

实施,128-129

implementation, 128129

意图,126

intent, 126

动机,126-127

motivation, 126127

参与者,128

participants, 128

相关模式, 129

related patterns, 129

Rho-微积分,296-297

rho-calculus, 296297

结构, 127

structure, 127

检索共享模式

Retrieve Shared pattern

适用性,241

applicability, 241

合作, 243

collaborations, 243

后果,243

consequences, 243

实施,243

implementation, 243

意图,240

intent, 240

动机,240-241

motivation, 240241

参与者,242

participants, 242

相关模式, 243

related patterns, 243

Rho-微积分, 310

rho-calculus, 310

结构,241-242

structure, 241242

Revert Method 模式

Revert Method pattern

适用性,177

applicability, 177

合作, 178

collaborations, 178

后果,178

consequences, 178

实施,178-179

implementation, 178179

意图,172

intent, 172

方法调用分类,179180

method call classification, 179180

动机,172-176

motivation, 172176

参与者,177-178

participants, 177178

相关模式, 179

related patterns, 179

Rho 微积分,301

rho-calculus, 301

结构,177

structure, 177

Rho 微积分

Rho-calculus

作曲,291-293

composition, 291293

点运算符,283

dot operators, 283

EDP 形式主义,287-291

EDP formalisms, 287291

同位素,285-286

isotopes, 285286

概述,281-282

overview, 281282

模式实例表示法和角色,293295

pattern instance notation and roles, 293295

减少规则,285-286,291-293

reduction rules, 285286, 291293

Reliance 运算符,282-284

reliance operators, 282284

范围界定, 283

scoping, 283

相似性,286-287

similarity, 286287

传递性,285-286

transitivity, 285286

Rho 演算,EDP 定义

Rho-calculus, EDP definitions

抽象界面298

Abstract Interface, 298

聚集300

Conglomeration, 300

创建对象295296

Create Object, 295296

代表集团303

Delegated Conglomeration, 303

代表团299

Delegation, 299

代表代表团306

Deputized Delegation, 306

代理重定向307

Deputized Redirection, 307

Extend 方法302

Extend Method, 302

继承298

Inheritance, 298

递归301

Recursion, 301

重定向递归303

Redirected Recursion, 303

重定向300

Redirection, 300

检索,296-297

Retrieve, 296297

还原方法301

Revert Method, 301

受信任的委派304

Trusted Delegation, 304

可信重定向,305

Trusted Redirection, 305

Rho 演算,四人帮定义

Rho-calculus, Gang of Four definitions

抽象工厂313

Abstract Factory, 313

责任链,318

Chain of Responsibility, 318

装饰师316

Decorator, 316

工厂法314-315

Factory Method, 314315

代理317

Proxy, 317

模板方法319

Template Method, 319

Rho 微积分,中间定义

Rho-calculus, intermediate definitions

完成方法308

Fulfill Method, 308

对象递归312

Object Recursion, 312

客观化者311

Objectifier, 311

检索新309

Retrieve New, 309

检索共享, 310

Retrieve Shared, 310

S

S

范围界定,22-24,283

Scoping, 2224, 283

自我意识设计,2-3

Selfconscious design, 23

车间领班。请参阅重定向模式

Shop foreman. See Redirection pattern.

西格玛微积分,282-285

Sigma-calculus, 282285

相似

Similarity

对象, 32

objects, 32

单例模式

Singleton pattern

作为 collapsed PINbox, 50

as collapsed PINbox, 50

社交网络邀请示例。请参阅 委托式聚合模式

Social network invites example. See Delegated Conglomeration pattern.

解决方案类别,22

Solution category, 22

SPQR(模式查询和识别系统),14-17

SPQR (System for Pattern Query and Recognition), 1417

堆叠 PIN 码框,5662

Stacked PINboxes, 5662

标准 PIN 码框,5155

Standard PINboxes, 5155

状态变化,42-44

State changes, 4244

策略模式

Strategy pattern

as pattern:UML 中的角色标记,48

as pattern:role tags in UML, 48

多个实例作为 pattern:UML 中的角色标记,49

multiple instances as pattern:role tags in UML, 49

多个实例作为 PIN 框,6162

multiple instances as PINboxes, 61, 62

结构模式,265-273

Structural patterns, 265273

同步方法调用,146

Synchronous method calls, 146

T

T

模板方法模式

Template Method pattern

作为工厂方法模式的一部分,279

as part of Factory Method pattern, 279

描述,275-279

description, 275279

在序列图中,51

in sequence diagram, 51

Rho-微积分, 319

rho-calculus, 319

汤姆索亚。请参阅重定向模式

Tom Sawyer. See Redirection pattern.

训练,108-109

Training, 108109

传递性,rho 微积分,285-286

Transitivity, rho-calculus, 285286

部落知识,5-8

Tribal knowledge, 58

部落神话,6-9,12

Tribal mythology, 69, 12

受信任的委派模式

Trusted Delegation pattern

适用性,204

applicability, 204

合作, 205

collaborations, 205

后果,205

consequences, 205

实施,205-206

implementation, 205206

意图,200

intent, 200

方法调用分类,207208

method call classification, 207208

动机,200-204

motivation, 200204

参与者,205

participants, 205

相关模式,206207

related patterns, 206207

Rho 微积分, 304

rho-calculus, 304

结构, 204

structure, 204

可信重定向模式。另请参阅 代理重定向模式;重定向递归模式;重定向模式

Trusted Redirection pattern. See also Deputized Redirection pattern; Redirected Recursion pattern; Redirection pattern.

适用性,212

applicability, 212

作为对象递归模式的一部分,798183254256

as part of Object Recursion pattern, 79, 8183, 254256

合作, 213

collaborations, 213

后果,213

consequences, 213

实施,213

implementation, 213

意图,209

intent, 209

方法调用分类,214215

method call classification, 214215

动机,209-211

motivation, 209211

参与者,212213

participants, 212213

相关模式,213214

related patterns, 213214

Rho 微积分,305

rho-calculus, 305

结构, 212

structure, 212

类型关系模式

Type Relation patterns

抽象界面140144

Abstract Interface, 140144

《继承》,130-139

Inheritance, 130139

键入 Reuse pattern。请参阅继承模式

Type Reuse pattern. See Inheritance pattern.

类型、分解模式、25-30

Types, decomposing patterns, 2530

U

U

UML(统一建模语言),46-49另请参阅 PIN (模式实例表示法) ;PINbox

UML (Unified Modeling Language), 4649. See also PIN (Pattern Instance Notation); PINboxes.

未命名命名空间,24

Unnamed namespaces, 24

无意识的设计,2-3

Unselfconscious design, 23

使用者关系,EDP,101102

Used-by relationships, EDPs, 101102

V

V

Virtual Method 模式。请参见抽象接口模式

Virtual Method pattern. See Abstract Interface pattern.

访客模式 10

Visitor pattern, 10

约翰·弗利赛德,2

Vlissides, John, 2

W

W

伍尔夫,鲍比,20,251

Woolf, Bobby, 20, 251

Z

Z

沃尔特·齐默,244

Zimmer, Walter, 244

脚注

Footnotes

第一章

Chapter 1

1. 您现在不需要知道访客模式是什么。我之所以选择它,只是因为对 Visitor 的讨论明确地解决了我所提出的观点。

1. You don’t need to know what the Visitor pattern is right now. I selected it only because the discussion of Visitor explicitly addresses the point I’m making.

2. 某些设计模式对于特定语言是唯一的,而且仅限于这些语言,但这些模式通常称为语言惯用语。在本文中,当我们使用术语 设计模式时,我们专门讨论与语言无关的概念。

2. Some design patterns are unique to specific languages, and only those languages, but those patterns are often called language idioms. In this text when we use the term design patterns, we are specifically talking about concepts that are language independent.

第 2 章

Chapter 2

1. 它必须完全相同吗?不!我们稍后会谈到这一点。

1. Does it have to be exactly the same? No! We’ll get to that in a moment.

2. 我们本来可以使用递归重定向,但在早期的反馈中,这个术语对大多数人来说意味着两个对象之间的循环,而由于某种原因,这个对象没有。此外,如果您愿意深入研究附录,还有一个正式的理由。

2. We could have gone with Recursive Redirection, but in early feedback, that term implied to most people a loop between two objects, while this one did not, for some reason. Also, there’s a formal reason given if you care to dive in to the appendix.

3. 如果您不熟悉术语多态性,请从继承 EDP 开始。这将为您提供一个很好的概念概述。

3. If you’re unfamiliar with the term polymorphism, start with the Inheritance EDP. That will give you a good overview of the concept.

4. 回想一下,依赖从调用方法到被调用的方法;在这种情况下,调用方法在包含被调用方法的类型的子类型中定义。

4. Recall that reliances go from the calling method to the method that is called; in this case the calling method is defined within a subtype of the type enclosing the method being called.

第 4 章

Chapter 4

1. 这并不严格地正确:一些语言确实允许通过后面的抽象来删除方法,但我们不会在这里讨论它们,因为这是一个高级的理论技巧,远远超出了本书的范围。此外,这样的删除将是一个非常不同的概念,不是吗?我们不会实现先前承诺的方法实现,我们会擦除对先前方法实现的访问。

1. This isn’t strictly true: some languages do allow for such removal of methods via later abstraction, but we won’t get into them here because that’s an advanced theoretical trick well beyond the scope of this book. Besides, such a removal would be a very different concept, wouldn’t it? We wouldn’t be fulfilling a prior promised method implementation, we’d be erasing access to prior method implementations.

2. 如果角色确实不同,则表明设计模式发生了根本性的变化。这种变化被称为模式的变体,是一些非常重大的事情已经改变的线索,比如正在解决的问题或它发生的环境。

2. If the roles do differ, it indicates a fundamental change to the design pattern. This change is called a variant of the pattern and is a clue that something very substantial has changed, such as the problem being solved or the context in which it is occurring.

3. 这是传递性的另一个例子,它是附录中描述的 ρ 演算的核心特征,也是赋予 EDP 真正力量的原因。你看,我说过我会一直鼓励你去读正式的片段。

3. This is another example of transitivity, and it is a core feature of the ρ-calculus described in the appendix, and what gives the EDPs their real power. See, I told you I was going to keep giving you nudges to go read the formal bits.

4. 并形成 Inheritance 的另一条腿,但为了清楚起见,我们目前将其省略了。ConcreteDecoratorADecorator

4. ConcreteDecoratorA and Decorator form another leg of Inheritance, but we’re leaving it out for clarity at the moment.

5. 再一次,我们现在忽略了超载。假设相似性不仅基于名称,还基于整个方法签名。

5. Once again, we’re ignoring overloading for now. Assume that similarity is based not only on the name but on the entire method signature.

第 5 章

Chapter 5

1. 这就是早期 C++ 系统在后台的样子,当时该工具是编译 C++ 代码的主要技术。如果您对面向对象语言和过程语言之间的边界感兴趣,那是一个很好的历史起点。cfront

1. This is what early C++ systems looked like under the hood, when the cfront tool was the primary technique for compiling C++ code. If you’re interested in the boundary between object-oriented and procedural languages, that is a good historical place to start.

2. 一种流行的语言是这条规则的一个明显例外:Objective-C 不像大多数语言那样在一种行为中强制执行分配和初始化。相反,它有一个 alloc-init 惯用语,旨在让开发人员将对象的内存管理和数据初始化作为单独的部分来处理。正如您所料,虽然它可以提供优化机会,但它也可能导致开发人员错误。

2. One popular language is a glaring exception to this rule: Objective-C does not enforce both allocation and initialization in one behavior, as most languages do. Instead, it has an alloc-init idiom intended to let developers handle the memory management and data initialization of objects as separate pieces. While it can offer optimization opportunities, as you might expect, it can also lead to developer error.

3. 顺便说一句,这个调用相同方法的超类版本是 Extend Method EDP 的一个示例。您可以阅读该条目以进行更深入的讨论。

3. By the way, this calling of the superclass’s version of the same method is an example of the Extend Method EDP. You can read that entry for a more thorough discussion.

4. 它还产生了我最喜欢的梳妆台之一。这是一辆位于西雅图的 60 年代中期大众甲壳虫,车牌为 FEATURE。因为任何错误,如果放置足够长的时间,最终都会成为某个地方某人所依赖的功能。

4. It also resulted in one of my favorite vanity plates. It was a mid-60s VW Beetle in Seattle with the license plate FEATURE. Because any bug, if left long enough, eventually becomes a feature that someone, somewhere, is relying on.

5. 您甚至可以说,就接口而言,继承可以被视为多个重定向模式实例的集合,其中有一个对象在它们之间共享。Redirect

5. You can even say that, as far as the interface is concerned, Inheritance can be considered a gathering together of multiple Redirection pattern instances, with one Redirect object being shared among them.

6. C++ 在 functor 概念中提供了类似的东西,用作调用机制,但 C# 采取了下一步,允许对预先存在的方法进行临时包装,以便以这种方式进行传递。operator()

6. C++ offers something similar in the functor concept using operator() as the calling mechanism, but C# takes the next step to allow for ad hoc wrapping of preexisting methods for passing around in this manner.

7. 可以提出一个很好的论点,假设数字输入控件仅限于数字,但有些 UI 工具包却反其道而行之。

7. A good argument can be made that having a text input widget control a numeric display does make rational design sense, assuming a numeric input control that was restricted to numerics only, but there are UI toolkits that went the other way.

附录 A

Appendix A

1. 除了一些例外,特别是埃菲尔铁塔;细节在这里太多了,但在 [35] 中有所介绍。如果你想进一步研究,请以研究协变和逆变返回类型的方法为起点。

1. With some exceptions, notably Eiffel; the details are too much for here, but are covered in [35]. If you wish to dive further, investigate covariant versus contravariant return types of methods as a starting point.

2. 是的,这似乎对类型玩得有点快和松散。我建议你参考 ς-calculus [1] 的文本,它应该很快就会清楚地表明情况并非如此。基础是坚实的。

2. Yes, this appears to be playing a bit fast and loose with types. I refer you to the text for ς-calculus [1], which should quickly make clear that this is not the case. The foundation is sound.

3. 从形式上看,子类型、子类化和继承并不都是一样的,但出于本书的目的,我们可以互换使用这两个术语。有关详细信息,请参见 [1]。

3. Subtyping, subclassing, and inheritance are not all the same from a formal point of view, but for the purposes of this book, we can use the terms interchangeably. See [1] for details.

4. 请原谅双关语,~ 和 表示法在下标中的小字体上太相似了,因此无法以这种方式使用。用户反馈导致采用 +/– 作为更明显的版本。

4. The ~ and notations were, pardon the pun, too similar at the small font sizes found in subscripts to be useful in this manner. User feedback led to the adoption of the +/– as a more visible version.